@rjsf/core 6.0.0-beta.21 → 6.0.0-beta.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/dist/core.umd.js +1580 -1771
  2. package/dist/index.cjs +2019 -2206
  3. package/dist/index.cjs.map +4 -4
  4. package/dist/index.esm.js +2011 -2182
  5. package/dist/index.esm.js.map +4 -4
  6. package/lib/components/Form.d.ts +50 -12
  7. package/lib/components/Form.d.ts.map +1 -1
  8. package/lib/components/Form.js +114 -31
  9. package/lib/components/fields/ArrayField.d.ts +2 -197
  10. package/lib/components/fields/ArrayField.d.ts.map +1 -1
  11. package/lib/components/fields/ArrayField.js +518 -536
  12. package/lib/components/fields/BooleanField.js +2 -2
  13. package/lib/components/fields/FallbackField.d.ts +7 -0
  14. package/lib/components/fields/FallbackField.d.ts.map +1 -0
  15. package/lib/components/fields/FallbackField.js +72 -0
  16. package/lib/components/fields/LayoutGridField.d.ts +109 -191
  17. package/lib/components/fields/LayoutGridField.d.ts.map +1 -1
  18. package/lib/components/fields/LayoutGridField.js +417 -444
  19. package/lib/components/fields/LayoutMultiSchemaField.d.ts.map +1 -1
  20. package/lib/components/fields/LayoutMultiSchemaField.js +2 -3
  21. package/lib/components/fields/MultiSchemaField.d.ts.map +1 -1
  22. package/lib/components/fields/MultiSchemaField.js +5 -3
  23. package/lib/components/fields/ObjectField.d.ts +2 -68
  24. package/lib/components/fields/ObjectField.d.ts.map +1 -1
  25. package/lib/components/fields/ObjectField.js +166 -168
  26. package/lib/components/fields/SchemaField.d.ts.map +1 -1
  27. package/lib/components/fields/SchemaField.js +35 -22
  28. package/lib/components/fields/StringField.js +2 -2
  29. package/lib/components/fields/index.d.ts.map +1 -1
  30. package/lib/components/fields/index.js +2 -0
  31. package/lib/components/templates/ArrayFieldItemButtonsTemplate.d.ts +3 -3
  32. package/lib/components/templates/ArrayFieldItemButtonsTemplate.d.ts.map +1 -1
  33. package/lib/components/templates/ArrayFieldItemButtonsTemplate.js +3 -8
  34. package/lib/components/templates/ArrayFieldItemTemplate.d.ts +3 -3
  35. package/lib/components/templates/ArrayFieldItemTemplate.d.ts.map +1 -1
  36. package/lib/components/templates/ArrayFieldItemTemplate.js +1 -1
  37. package/lib/components/templates/ArrayFieldTemplate.d.ts +1 -1
  38. package/lib/components/templates/ArrayFieldTemplate.d.ts.map +1 -1
  39. package/lib/components/templates/ArrayFieldTemplate.js +2 -4
  40. package/lib/components/templates/BaseInputTemplate.js +2 -2
  41. package/lib/components/templates/FallbackFieldTemplate.d.ts +7 -0
  42. package/lib/components/templates/FallbackFieldTemplate.d.ts.map +1 -0
  43. package/lib/components/templates/FallbackFieldTemplate.js +12 -0
  44. package/lib/components/templates/ObjectFieldTemplate.js +2 -2
  45. package/lib/components/templates/WrapIfAdditionalTemplate.js +2 -2
  46. package/lib/components/templates/index.d.ts.map +1 -1
  47. package/lib/components/templates/index.js +2 -0
  48. package/lib/components/widgets/AltDateWidget.d.ts +1 -1
  49. package/lib/components/widgets/AltDateWidget.d.ts.map +1 -1
  50. package/lib/components/widgets/AltDateWidget.js +5 -43
  51. package/lib/components/widgets/CheckboxWidget.d.ts +1 -1
  52. package/lib/components/widgets/CheckboxWidget.d.ts.map +1 -1
  53. package/lib/components/widgets/CheckboxWidget.js +2 -2
  54. package/lib/components/widgets/CheckboxesWidget.d.ts +1 -1
  55. package/lib/components/widgets/CheckboxesWidget.d.ts.map +1 -1
  56. package/lib/components/widgets/CheckboxesWidget.js +2 -2
  57. package/lib/components/widgets/FileWidget.d.ts.map +1 -1
  58. package/lib/components/widgets/FileWidget.js +7 -87
  59. package/lib/components/widgets/HiddenWidget.d.ts +1 -1
  60. package/lib/components/widgets/HiddenWidget.d.ts.map +1 -1
  61. package/lib/components/widgets/HiddenWidget.js +2 -2
  62. package/lib/components/widgets/RadioWidget.d.ts +1 -1
  63. package/lib/components/widgets/RadioWidget.d.ts.map +1 -1
  64. package/lib/components/widgets/RadioWidget.js +2 -2
  65. package/lib/components/widgets/RatingWidget.d.ts +1 -1
  66. package/lib/components/widgets/RatingWidget.d.ts.map +1 -1
  67. package/lib/components/widgets/RatingWidget.js +2 -2
  68. package/lib/components/widgets/SelectWidget.d.ts +1 -1
  69. package/lib/components/widgets/SelectWidget.d.ts.map +1 -1
  70. package/lib/components/widgets/SelectWidget.js +2 -2
  71. package/lib/components/widgets/TextareaWidget.d.ts +1 -1
  72. package/lib/components/widgets/TextareaWidget.d.ts.map +1 -1
  73. package/lib/components/widgets/TextareaWidget.js +2 -2
  74. package/lib/getDefaultRegistry.d.ts.map +1 -1
  75. package/lib/getDefaultRegistry.js +5 -1
  76. package/lib/getTestRegistry.d.ts.map +1 -1
  77. package/lib/getTestRegistry.js +5 -1
  78. package/lib/tsconfig.tsbuildinfo +1 -1
  79. package/package.json +21 -5
  80. package/src/components/Form.tsx +176 -43
  81. package/src/components/fields/ArrayField.tsx +849 -758
  82. package/src/components/fields/BooleanField.tsx +2 -2
  83. package/src/components/fields/FallbackField.tsx +157 -0
  84. package/src/components/fields/LayoutGridField.tsx +613 -600
  85. package/src/components/fields/LayoutMultiSchemaField.tsx +4 -5
  86. package/src/components/fields/MultiSchemaField.tsx +30 -25
  87. package/src/components/fields/ObjectField.tsx +315 -242
  88. package/src/components/fields/OptionalDataControlsField.tsx +1 -1
  89. package/src/components/fields/SchemaField.tsx +46 -70
  90. package/src/components/fields/StringField.tsx +2 -2
  91. package/src/components/fields/index.ts +2 -0
  92. package/src/components/templates/ArrayFieldItemButtonsTemplate.tsx +11 -16
  93. package/src/components/templates/ArrayFieldItemTemplate.tsx +3 -3
  94. package/src/components/templates/ArrayFieldTemplate.tsx +2 -13
  95. package/src/components/templates/BaseInputTemplate.tsx +2 -2
  96. package/src/components/templates/FallbackFieldTemplate.tsx +28 -0
  97. package/src/components/templates/ObjectFieldTemplate.tsx +2 -2
  98. package/src/components/templates/WrapIfAdditionalTemplate.tsx +4 -4
  99. package/src/components/templates/index.ts +2 -0
  100. package/src/components/widgets/AltDateWidget.tsx +8 -124
  101. package/src/components/widgets/CheckboxWidget.tsx +2 -1
  102. package/src/components/widgets/CheckboxesWidget.tsx +2 -1
  103. package/src/components/widgets/FileWidget.tsx +11 -102
  104. package/src/components/widgets/HiddenWidget.tsx +2 -1
  105. package/src/components/widgets/RadioWidget.tsx +2 -1
  106. package/src/components/widgets/RatingWidget.tsx +2 -1
  107. package/src/components/widgets/SelectWidget.tsx +2 -1
  108. package/src/components/widgets/TextareaWidget.tsx +2 -1
  109. package/src/getDefaultRegistry.ts +5 -1
  110. package/src/getTestRegistry.tsx +5 -1
@@ -1,8 +1,7 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { Component } from 'react';
3
- import { getTemplate, getWidget, getUiOptions, isFixedItems, allowAdditionalItems, isCustomWidget, isFormDataAvailable, optionsList, shouldRenderOptionalField, toFieldPathId, TranslatableString, ITEMS_KEY, } from '@rjsf/utils';
2
+ import { useCallback, useMemo, useState } from 'react';
3
+ import { allowAdditionalItems, getTemplate, getUiOptions, getWidget, hashObject, isCustomWidget, isFixedItems, isFormDataAvailable, optionsList, shouldRenderOptionalField, toFieldPathId, useDeepCompareMemo, ITEMS_KEY, ID_KEY, TranslatableString, } from '@rjsf/utils';
4
4
  import cloneDeep from 'lodash-es/cloneDeep.js';
5
- import get from 'lodash-es/get.js';
6
5
  import isObject from 'lodash-es/isObject.js';
7
6
  import set from 'lodash-es/set.js';
8
7
  import uniqueId from 'lodash-es/uniqueId.js';
@@ -36,108 +35,422 @@ function keyedToPlainFormData(keyedFormData) {
36
35
  }
37
36
  return [];
38
37
  }
39
- /** The `ArrayField` component is used to render a field in the schema that is of type `array`. It supports both normal
40
- * and fixed array, allowing user to add and remove elements from the array data.
38
+ /** Determines whether the item described in the schema is always required, which is determined by whether any item
39
+ * may be null.
40
+ *
41
+ * @param itemSchema - The schema for the item
42
+ * @return - True if the item schema type does not contain the "null" type
41
43
  */
42
- class ArrayField extends Component {
43
- /** Constructs an `ArrayField` from the `props`, generating the initial keyed data from the `formData`
44
- *
45
- * @param props - The `FieldProps` for this template
46
- */
47
- constructor(props) {
48
- super(props);
49
- const { formData } = props;
50
- const keyedFormData = generateKeyedFormData(formData);
51
- this.state = {
52
- keyedFormData,
53
- updatedKeyedFormData: false,
54
- };
44
+ function isItemRequired(itemSchema) {
45
+ if (Array.isArray(itemSchema.type)) {
46
+ // While we don't yet support composite/nullable jsonschema types, it's
47
+ // future-proof to check for requirement against these.
48
+ return !itemSchema.type.includes('null');
55
49
  }
56
- /** React lifecycle method that is called when the props are about to change allowing the state to be updated. It
57
- * regenerates the keyed form data and returns it
58
- *
59
- * @param nextProps - The next set of props data
60
- * @param prevState - The previous set of state data
61
- */
62
- static getDerivedStateFromProps(nextProps, prevState) {
63
- // Don't call getDerivedStateFromProps if keyed formdata was just updated.
64
- if (prevState.updatedKeyedFormData) {
65
- return {
66
- updatedKeyedFormData: false,
67
- };
50
+ // All non-null array item types are inherently required by design
51
+ return itemSchema.type !== 'null';
52
+ }
53
+ /** Determines whether more items can be added to the array. If the uiSchema indicates the array doesn't allow adding
54
+ * then false is returned. Otherwise, if the schema indicates that there are a maximum number of items and the
55
+ * `formData` matches that value, then false is returned, otherwise true is returned.
56
+ *
57
+ * @param registry - The registry
58
+ * @param schema - The schema for the field
59
+ * @param formItems - The list of items in the form
60
+ * @param [uiSchema] - The UiSchema for the field
61
+ * @returns - True if the item is addable otherwise false
62
+ */
63
+ function canAddItem(registry, schema, formItems, uiSchema) {
64
+ let { addable } = getUiOptions(uiSchema, registry.globalUiOptions);
65
+ if (addable !== false) {
66
+ // if ui:options.addable was not explicitly set to false, we can add
67
+ // another item if we have not exceeded maxItems yet
68
+ if (schema.maxItems !== undefined) {
69
+ addable = formItems.length < schema.maxItems;
70
+ }
71
+ else {
72
+ addable = true;
68
73
  }
69
- const nextFormData = Array.isArray(nextProps.formData) ? nextProps.formData : [];
70
- const previousKeyedFormData = prevState.keyedFormData || [];
71
- const newKeyedFormData = nextFormData.length === previousKeyedFormData.length
72
- ? previousKeyedFormData.map((previousKeyedFormDatum, index) => {
73
- return {
74
- key: previousKeyedFormDatum.key,
75
- item: nextFormData[index],
76
- };
77
- })
78
- : generateKeyedFormData(nextFormData);
79
- return {
80
- keyedFormData: newKeyedFormData,
81
- };
82
- }
83
- /** Returns the appropriate title for an item by getting first the title from the schema.items, then falling back to
84
- * the description from the schema.items, and finally the string "Item"
85
- */
86
- get itemTitle() {
87
- const { schema, registry } = this.props;
88
- const { translateString } = registry;
89
- return get(schema, [ITEMS_KEY, 'title'], get(schema, [ITEMS_KEY, 'description'], translateString(TranslatableString.ArrayItemTitle)));
90
74
  }
91
- /** Determines whether the item described in the schema is always required, which is determined by whether any item
92
- * may be null.
93
- *
94
- * @param itemSchema - The schema for the item
95
- * @return - True if the item schema type does not contain the "null" type
96
- */
97
- isItemRequired(itemSchema) {
98
- if (Array.isArray(itemSchema.type)) {
99
- // While we don't yet support composite/nullable jsonschema types, it's
100
- // future-proof to check for requirement against these.
101
- return !itemSchema.type.includes('null');
75
+ return addable;
76
+ }
77
+ /** Helper method to compute item UI schema for both normal and fixed arrays
78
+ * Handles both static object and dynamic function cases
79
+ *
80
+ * @param uiSchema - The parent UI schema containing items definition
81
+ * @param item - The item data
82
+ * @param index - The index of the item
83
+ * @param formContext - The form context
84
+ * @returns The computed UI schema for the item
85
+ */
86
+ function computeItemUiSchema(uiSchema, item, index, formContext) {
87
+ if (typeof uiSchema.items === 'function') {
88
+ try {
89
+ // Call the function with item data, index, and form context
90
+ // TypeScript now correctly infers the types thanks to the ArrayElement type in UiSchema
91
+ const result = uiSchema.items(item, index, formContext);
92
+ // Only use the result if it's truthy
93
+ return result;
94
+ }
95
+ catch (e) {
96
+ console.error(`Error executing dynamic uiSchema.items function for item at index ${index}:`, e);
97
+ // Fall back to undefined to allow the field to still render
98
+ return undefined;
102
99
  }
103
- // All non-null array item types are inherently required by design
104
- return itemSchema.type !== 'null';
105
100
  }
106
- /** Determines whether more items can be added to the array. If the uiSchema indicates the array doesn't allow adding
107
- * then false is returned. Otherwise, if the schema indicates that there are a maximum number of items and the
108
- * `formData` matches that value, then false is returned, otherwise true is returned.
109
- *
110
- * @param formItems - The list of items in the form
111
- * @returns - True if the item is addable otherwise false
112
- */
113
- canAddItem(formItems) {
114
- const { schema, uiSchema, registry } = this.props;
115
- let { addable } = getUiOptions(uiSchema, registry.globalUiOptions);
116
- if (addable !== false) {
117
- // if ui:options.addable was not explicitly set to false, we can add
118
- // another item if we have not exceeded maxItems yet
119
- if (schema.maxItems !== undefined) {
120
- addable = formItems.length < schema.maxItems;
101
+ else {
102
+ // Static object case - preserve undefined to maintain backward compatibility
103
+ return uiSchema.items;
104
+ }
105
+ }
106
+ /** Returns the default form information for an item based on the schema for that item. Deals with the possibility
107
+ * that the schema is fixed and allows additional items.
108
+ */
109
+ function getNewFormDataRow(registry, schema) {
110
+ const { schemaUtils } = registry;
111
+ let itemSchema = schema.items;
112
+ if (isFixedItems(schema) && allowAdditionalItems(schema)) {
113
+ itemSchema = schema.additionalItems;
114
+ }
115
+ // Cast this as a T to work around schema utils being for T[] caused by the FieldProps<T[], S, F> call on the class
116
+ return schemaUtils.getDefaultFormState(itemSchema);
117
+ }
118
+ /** Renders an array as a set of checkboxes using the 'select' widget
119
+ */
120
+ function ArrayAsMultiSelect(props) {
121
+ const { schema, fieldPathId, uiSchema, formData: items = [], disabled = false, readonly = false, autofocus = false, required = false, placeholder, onBlur, onFocus, registry, rawErrors, name, onSelectChange, } = props;
122
+ const { widgets, schemaUtils, globalFormOptions, globalUiOptions } = registry;
123
+ const itemsSchema = schemaUtils.retrieveSchema(schema.items, items);
124
+ const enumOptions = optionsList(itemsSchema, uiSchema);
125
+ const { widget = 'select', title: uiTitle, ...options } = getUiOptions(uiSchema, globalUiOptions);
126
+ const Widget = getWidget(schema, widget, widgets);
127
+ const label = uiTitle ?? schema.title ?? name;
128
+ const displayLabel = schemaUtils.getDisplayLabel(schema, uiSchema, globalUiOptions);
129
+ // For custom widgets with multiple=true, generate a fieldPathId with isMultiValue flag
130
+ const multiValueFieldPathId = useDeepCompareMemo(toFieldPathId('', globalFormOptions, fieldPathId, true));
131
+ return (_jsx(Widget, { id: multiValueFieldPathId[ID_KEY], name: name, multiple: true, onChange: onSelectChange, onBlur: onBlur, onFocus: onFocus, options: { ...options, enumOptions }, schema: schema, uiSchema: uiSchema, registry: registry, value: items, disabled: disabled, readonly: readonly, required: required, label: label, hideLabel: !displayLabel, placeholder: placeholder, autofocus: autofocus, rawErrors: rawErrors, htmlName: multiValueFieldPathId.name }));
132
+ }
133
+ /** Renders an array using the custom widget provided by the user in the `uiSchema`
134
+ */
135
+ function ArrayAsCustomWidget(props) {
136
+ const { schema, fieldPathId, uiSchema, disabled = false, readonly = false, autofocus = false, required = false, hideError, placeholder, onBlur, onFocus, formData: items = [], registry, rawErrors, name, onSelectChange, } = props;
137
+ const { widgets, schemaUtils, globalFormOptions, globalUiOptions } = registry;
138
+ const { widget, title: uiTitle, ...options } = getUiOptions(uiSchema, globalUiOptions);
139
+ const Widget = getWidget(schema, widget, widgets);
140
+ const label = uiTitle ?? schema.title ?? name;
141
+ const displayLabel = schemaUtils.getDisplayLabel(schema, uiSchema, globalUiOptions);
142
+ // For custom widgets with multiple=true, generate a fieldPathId with isMultiValue flag
143
+ const multiValueFieldPathId = useDeepCompareMemo(toFieldPathId('', globalFormOptions, fieldPathId, true));
144
+ return (_jsx(Widget, { id: multiValueFieldPathId[ID_KEY], name: name, multiple: true, onChange: onSelectChange, onBlur: onBlur, onFocus: onFocus, options: options, schema: schema, uiSchema: uiSchema, registry: registry, value: items, disabled: disabled, readonly: readonly, hideError: hideError, required: required, label: label, hideLabel: !displayLabel, placeholder: placeholder, autofocus: autofocus, rawErrors: rawErrors, htmlName: multiValueFieldPathId.name }));
145
+ }
146
+ /** Renders an array of files using the `FileWidget`
147
+ */
148
+ function ArrayAsFiles(props) {
149
+ const { schema, uiSchema, fieldPathId, name, disabled = false, readonly = false, autofocus = false, required = false, onBlur, onFocus, registry, formData: items = [], rawErrors, onSelectChange, } = props;
150
+ const { widgets, schemaUtils, globalFormOptions, globalUiOptions } = registry;
151
+ const { widget = 'files', title: uiTitle, ...options } = getUiOptions(uiSchema, globalUiOptions);
152
+ const Widget = getWidget(schema, widget, widgets);
153
+ const label = uiTitle ?? schema.title ?? name;
154
+ const displayLabel = schemaUtils.getDisplayLabel(schema, uiSchema, globalUiOptions);
155
+ // For custom widgets with multiple=true, generate a fieldPathId with isMultiValue flag
156
+ const multiValueFieldPathId = useDeepCompareMemo(toFieldPathId('', globalFormOptions, fieldPathId, true));
157
+ return (_jsx(Widget, { options: options, id: multiValueFieldPathId[ID_KEY], name: name, multiple: true, onChange: onSelectChange, onBlur: onBlur, onFocus: onFocus, schema: schema, uiSchema: uiSchema, value: items, disabled: disabled, readonly: readonly, required: required, registry: registry, autofocus: autofocus, rawErrors: rawErrors, label: label, hideLabel: !displayLabel, htmlName: multiValueFieldPathId.name }));
158
+ }
159
+ /** Renders the individual array item using a `SchemaField` along with the additional properties that are needed to
160
+ * render the whole of the `ArrayFieldItemTemplate`.
161
+ */
162
+ function ArrayFieldItem(props) {
163
+ const { itemKey, index, name, disabled, hideError, readonly, registry, uiOptions, parentUiSchema, canAdd, canRemove = true, canMoveUp, canMoveDown, itemSchema, itemData, itemUiSchema, itemFieldPathId, itemErrorSchema, autofocus, onBlur, onFocus, onChange, rawErrors, totalItems, title, handleAddItem, handleCopyItem, handleRemoveItem, handleReorderItems, } = props;
164
+ const { fields: { ArraySchemaField, SchemaField }, } = registry;
165
+ const fieldPathId = useDeepCompareMemo(itemFieldPathId);
166
+ const ItemSchemaField = ArraySchemaField || SchemaField;
167
+ const ArrayFieldItemTemplate = getTemplate('ArrayFieldItemTemplate', registry, uiOptions);
168
+ const { orderable = true, removable = true, copyable = false } = uiOptions;
169
+ const has = {
170
+ moveUp: orderable && canMoveUp,
171
+ moveDown: orderable && canMoveDown,
172
+ copy: copyable && canAdd,
173
+ remove: removable && canRemove,
174
+ toolbar: false,
175
+ };
176
+ has.toolbar = Object.keys(has).some((key) => has[key]);
177
+ const onAddItem = useCallback((event) => {
178
+ handleAddItem(event, index + 1);
179
+ }, [handleAddItem, index]);
180
+ const onCopyItem = useCallback((event) => {
181
+ handleCopyItem(event, index);
182
+ }, [handleCopyItem, index]);
183
+ const onRemoveItem = useCallback((event) => {
184
+ handleRemoveItem(event, index);
185
+ }, [handleRemoveItem, index]);
186
+ const onMoveUpItem = useCallback((event) => {
187
+ handleReorderItems(event, index, index - 1);
188
+ }, [handleReorderItems, index]);
189
+ const onMoveDownItem = useCallback((event) => {
190
+ handleReorderItems(event, index, index + 1);
191
+ }, [handleReorderItems, index]);
192
+ const templateProps = {
193
+ children: (_jsx(ItemSchemaField, { name: name, title: title, index: index, schema: itemSchema, uiSchema: itemUiSchema, formData: itemData, errorSchema: itemErrorSchema, fieldPathId: fieldPathId, required: isItemRequired(itemSchema), onChange: onChange, onBlur: onBlur, onFocus: onFocus, registry: registry, disabled: disabled, readonly: readonly, hideError: hideError, autofocus: autofocus, rawErrors: rawErrors })),
194
+ buttonsProps: {
195
+ fieldPathId,
196
+ disabled,
197
+ readonly,
198
+ canAdd,
199
+ hasCopy: has.copy,
200
+ hasMoveUp: has.moveUp,
201
+ hasMoveDown: has.moveDown,
202
+ hasRemove: has.remove,
203
+ index: index,
204
+ totalItems,
205
+ onAddItem,
206
+ onCopyItem,
207
+ onRemoveItem,
208
+ onMoveUpItem,
209
+ onMoveDownItem,
210
+ registry,
211
+ schema: itemSchema,
212
+ uiSchema: itemUiSchema,
213
+ },
214
+ itemKey,
215
+ className: 'rjsf-array-item',
216
+ disabled,
217
+ hasToolbar: has.toolbar,
218
+ index,
219
+ totalItems,
220
+ readonly,
221
+ registry,
222
+ schema: itemSchema,
223
+ uiSchema: itemUiSchema,
224
+ parentUiSchema,
225
+ };
226
+ return _jsx(ArrayFieldItemTemplate, { ...templateProps });
227
+ }
228
+ /** Renders a normal array without any limitations of length
229
+ */
230
+ function NormalArray(props) {
231
+ const { schema, uiSchema = {}, errorSchema, fieldPathId, formData: formDataFromProps, name, title, disabled = false, readonly = false, autofocus = false, required = false, hideError = false, registry, onBlur, onFocus, rawErrors, onChange, keyedFormData, handleAddItem, handleCopyItem, handleRemoveItem, handleReorderItems, } = props;
232
+ const fieldTitle = schema.title || title || name;
233
+ const { schemaUtils, fields, formContext, globalFormOptions, globalUiOptions } = registry;
234
+ const { OptionalDataControlsField } = fields;
235
+ const uiOptions = getUiOptions(uiSchema, globalUiOptions);
236
+ const _schemaItems = isObject(schema.items) ? schema.items : {};
237
+ const itemsSchema = schemaUtils.retrieveSchema(_schemaItems);
238
+ const formData = keyedToPlainFormData(keyedFormData);
239
+ const renderOptionalField = shouldRenderOptionalField(registry, schema, required, uiSchema);
240
+ const hasFormData = isFormDataAvailable(formDataFromProps);
241
+ const canAdd = canAddItem(registry, schema, formData, uiSchema) && (!renderOptionalField || hasFormData);
242
+ const actualFormData = hasFormData ? keyedFormData : [];
243
+ const extraClass = renderOptionalField ? ' rjsf-optional-array-field' : '';
244
+ const optionalDataControl = renderOptionalField ? _jsx(OptionalDataControlsField, { ...props }) : undefined;
245
+ const arrayProps = {
246
+ canAdd,
247
+ items: actualFormData.map((keyedItem, index) => {
248
+ const { key, item } = keyedItem;
249
+ // While we are actually dealing with a single item of type T, the types require a T[], so cast
250
+ const itemCast = item;
251
+ const itemSchema = schemaUtils.retrieveSchema(_schemaItems, itemCast);
252
+ const itemErrorSchema = errorSchema ? errorSchema[index] : undefined;
253
+ const itemFieldPathId = toFieldPathId(index, globalFormOptions, fieldPathId);
254
+ // Compute the item UI schema using the helper method
255
+ const itemUiSchema = computeItemUiSchema(uiSchema, item, index, formContext);
256
+ const itemProps = {
257
+ itemKey: key,
258
+ index,
259
+ name: name && `${name}-${index}`,
260
+ registry,
261
+ uiOptions,
262
+ hideError,
263
+ readonly,
264
+ disabled,
265
+ required,
266
+ title: fieldTitle ? `${fieldTitle}-${index + 1}` : undefined,
267
+ canAdd,
268
+ canMoveUp: index > 0,
269
+ canMoveDown: index < formData.length - 1,
270
+ itemSchema,
271
+ itemFieldPathId,
272
+ itemErrorSchema,
273
+ itemData: itemCast,
274
+ itemUiSchema,
275
+ autofocus: autofocus && index === 0,
276
+ onBlur,
277
+ onFocus,
278
+ rawErrors,
279
+ totalItems: keyedFormData.length,
280
+ handleAddItem,
281
+ handleCopyItem,
282
+ handleRemoveItem,
283
+ handleReorderItems,
284
+ onChange,
285
+ };
286
+ return _jsx(ArrayFieldItem, { ...itemProps }, key);
287
+ }),
288
+ className: `rjsf-field rjsf-field-array rjsf-field-array-of-${itemsSchema.type}${extraClass}`,
289
+ disabled,
290
+ fieldPathId,
291
+ uiSchema,
292
+ onAddClick: handleAddItem,
293
+ readonly,
294
+ required,
295
+ schema,
296
+ title: fieldTitle,
297
+ formData,
298
+ rawErrors,
299
+ registry,
300
+ optionalDataControl,
301
+ };
302
+ const Template = getTemplate('ArrayFieldTemplate', registry, uiOptions);
303
+ return _jsx(Template, { ...arrayProps });
304
+ }
305
+ /** Renders an array that has a maximum limit of items
306
+ */
307
+ function FixedArray(props) {
308
+ const { schema, uiSchema = {}, formData, errorSchema, fieldPathId, name, title, disabled = false, readonly = false, autofocus = false, required = false, hideError = false, registry, onBlur, onFocus, rawErrors, keyedFormData, onChange, handleAddItem, handleCopyItem, handleRemoveItem, handleReorderItems, } = props;
309
+ let { formData: items = [] } = props;
310
+ const fieldTitle = schema.title || title || name;
311
+ const { schemaUtils, fields, formContext, globalFormOptions, globalUiOptions } = registry;
312
+ const uiOptions = getUiOptions(uiSchema, globalUiOptions);
313
+ const { OptionalDataControlsField } = fields;
314
+ const renderOptionalField = shouldRenderOptionalField(registry, schema, required, uiSchema);
315
+ const hasFormData = isFormDataAvailable(formData);
316
+ const _schemaItems = isObject(schema.items) ? schema.items : [];
317
+ const itemSchemas = _schemaItems.map((item, index) => schemaUtils.retrieveSchema(item, items[index]));
318
+ const additionalSchema = isObject(schema.additionalItems)
319
+ ? schemaUtils.retrieveSchema(schema.additionalItems, formData)
320
+ : null;
321
+ if (items.length < itemSchemas.length) {
322
+ // to make sure at least all fixed items are generated
323
+ items = items.concat(new Array(itemSchemas.length - items.length));
324
+ }
325
+ const actualFormData = hasFormData ? keyedFormData : [];
326
+ const extraClass = renderOptionalField ? ' rjsf-optional-array-field' : '';
327
+ const optionalDataControl = renderOptionalField ? _jsx(OptionalDataControlsField, { ...props }) : undefined;
328
+ // These are the props passed into the render function
329
+ const canAdd = canAddItem(registry, schema, items, uiSchema) &&
330
+ !!additionalSchema &&
331
+ (!renderOptionalField || hasFormData);
332
+ const arrayProps = {
333
+ canAdd,
334
+ className: `rjsf-field rjsf-field-array rjsf-field-array-fixed-items${extraClass}`,
335
+ disabled,
336
+ fieldPathId,
337
+ formData,
338
+ items: actualFormData.map((keyedItem, index) => {
339
+ const { key, item } = keyedItem;
340
+ // While we are actually dealing with a single item of type T, the types require a T[], so cast
341
+ const itemCast = item;
342
+ const additional = index >= itemSchemas.length;
343
+ const itemSchema = (additional && isObject(schema.additionalItems)
344
+ ? schemaUtils.retrieveSchema(schema.additionalItems, itemCast)
345
+ : itemSchemas[index]) || {};
346
+ const itemFieldPathId = toFieldPathId(index, globalFormOptions, fieldPathId);
347
+ // Compute the item UI schema - handle both static and dynamic cases
348
+ let itemUiSchema;
349
+ if (additional) {
350
+ // For additional items, use additionalItems uiSchema
351
+ itemUiSchema = uiSchema.additionalItems;
121
352
  }
122
353
  else {
123
- addable = true;
354
+ // For fixed items, uiSchema.items can be an array, a function, or a single object
355
+ if (Array.isArray(uiSchema.items)) {
356
+ itemUiSchema = uiSchema.items[index];
357
+ }
358
+ else {
359
+ // Use the helper method for function or static object cases
360
+ itemUiSchema = computeItemUiSchema(uiSchema, item, index, formContext);
361
+ }
124
362
  }
125
- }
126
- return addable;
127
- }
128
- /** Returns the default form information for an item based on the schema for that item. Deals with the possibility
129
- * that the schema is fixed and allows additional items.
130
- */
131
- _getNewFormDataRow = () => {
132
- const { schema, registry } = this.props;
133
- const { schemaUtils } = registry;
134
- let itemSchema = schema.items;
135
- if (isFixedItems(schema) && allowAdditionalItems(schema)) {
136
- itemSchema = schema.additionalItems;
137
- }
138
- // Cast this as a T to work around schema utils being for T[] caused by the FieldProps<T[], S, F> call on the class
139
- return schemaUtils.getDefaultFormState(itemSchema);
363
+ const itemErrorSchema = errorSchema ? errorSchema[index] : undefined;
364
+ const itemProps = {
365
+ index,
366
+ itemKey: key,
367
+ name: name && `${name}-${index}`,
368
+ registry,
369
+ uiOptions,
370
+ hideError,
371
+ readonly,
372
+ disabled,
373
+ required,
374
+ title: fieldTitle ? `${fieldTitle}-${index + 1}` : undefined,
375
+ canAdd,
376
+ canRemove: additional,
377
+ canMoveUp: index >= itemSchemas.length + 1,
378
+ canMoveDown: additional && index < items.length - 1,
379
+ itemSchema,
380
+ itemData: itemCast,
381
+ itemUiSchema,
382
+ itemFieldPathId,
383
+ itemErrorSchema,
384
+ autofocus: autofocus && index === 0,
385
+ onBlur,
386
+ onFocus,
387
+ rawErrors,
388
+ totalItems: keyedFormData.length,
389
+ onChange,
390
+ handleAddItem,
391
+ handleCopyItem,
392
+ handleRemoveItem,
393
+ handleReorderItems,
394
+ };
395
+ return _jsx(ArrayFieldItem, { ...itemProps }, key);
396
+ }),
397
+ onAddClick: handleAddItem,
398
+ readonly,
399
+ required,
400
+ registry,
401
+ schema,
402
+ uiSchema,
403
+ title: fieldTitle,
404
+ errorSchema,
405
+ rawErrors,
406
+ optionalDataControl,
140
407
  };
408
+ const Template = getTemplate('ArrayFieldTemplate', registry, uiOptions);
409
+ return _jsx(Template, { ...arrayProps });
410
+ }
411
+ /** A custom hook that handles the updating of the keyedFormData from an external `formData` change as well as
412
+ * internally by the `ArrayField`. If there was an external `formData` change, then the `keyedFormData` is recomputed
413
+ * in order to preserve the unique keys from the old `keyedFormData` to the new `formData`. Along with the
414
+ * `keyedFormData` this hook also returns an `updateKeyedFormData()` function for use by the `ArrayField`. The detection
415
+ * of external `formData` are handled by storing the hash of that `formData` along with the `keyedFormData` associated
416
+ * with it. The `updateKeyedFormData()` will update that hash whenever the `keyedFormData` is modified and as well as
417
+ * returning the plain `formData` from the `keyedFormData`.
418
+ */
419
+ function useKeyedFormData(formData = []) {
420
+ const newHash = useMemo(() => hashObject(formData), [formData]);
421
+ const [state, setState] = useState(() => ({
422
+ formDataHash: newHash,
423
+ keyedFormData: generateKeyedFormData(formData),
424
+ }));
425
+ let { keyedFormData, formDataHash } = state;
426
+ if (newHash !== formDataHash) {
427
+ const nextFormData = Array.isArray(formData) ? formData : [];
428
+ const previousKeyedFormData = keyedFormData || [];
429
+ keyedFormData =
430
+ nextFormData.length === previousKeyedFormData.length
431
+ ? previousKeyedFormData.map((previousKeyedFormDatum, index) => ({
432
+ key: previousKeyedFormDatum.key,
433
+ item: nextFormData[index],
434
+ }))
435
+ : generateKeyedFormData(nextFormData);
436
+ formDataHash = newHash;
437
+ setState({ formDataHash, keyedFormData });
438
+ }
439
+ const updateKeyedFormData = useCallback((newData) => {
440
+ const plainFormData = keyedToPlainFormData(newData);
441
+ const newHash = hashObject(plainFormData);
442
+ setState({ formDataHash: newHash, keyedFormData: newData });
443
+ return plainFormData;
444
+ }, []);
445
+ return { keyedFormData, updateKeyedFormData };
446
+ }
447
+ /** The `ArrayField` component is used to render a field in the schema that is of type `array`. It supports both normal
448
+ * and fixed array, allowing user to add and remove elements from the array data.
449
+ */
450
+ export default function ArrayField(props) {
451
+ const { schema, uiSchema, errorSchema, fieldPathId, registry, formData, onChange } = props;
452
+ const { schemaUtils, translateString } = registry;
453
+ const { keyedFormData, updateKeyedFormData } = useKeyedFormData(formData);
141
454
  /** Callback handler for when the user clicks on the add or add at index buttons. Creates a new row of keyed form data
142
455
  * either at the end of the list (when index is not specified) or inserted at the `index` when it is, adding it into
143
456
  * the state, and then returning `onChange()` with the plain form data converted from the keyed data
@@ -145,13 +458,10 @@ class ArrayField extends Component {
145
458
  * @param event - The event for the click
146
459
  * @param [index] - The optional index at which to add the new data
147
460
  */
148
- _handleAddClick(event, index) {
461
+ const handleAddItem = useCallback((event, index) => {
149
462
  if (event) {
150
463
  event.preventDefault();
151
464
  }
152
- const { onChange, errorSchema, fieldPathId } = this.props;
153
- const { keyedFormData } = this.state;
154
- // refs #195: revalidate to ensure properly reindexing errors
155
465
  let newErrorSchema;
156
466
  if (errorSchema) {
157
467
  newErrorSchema = {};
@@ -167,7 +477,7 @@ class ArrayField extends Component {
167
477
  }
168
478
  const newKeyedFormDataRow = {
169
479
  key: generateRowId(),
170
- item: this._getNewFormDataRow(),
480
+ item: getNewFormDataRow(registry, schema),
171
481
  };
172
482
  const newKeyedFormData = [...keyedFormData];
173
483
  if (index !== undefined) {
@@ -176,111 +486,71 @@ class ArrayField extends Component {
176
486
  else {
177
487
  newKeyedFormData.push(newKeyedFormDataRow);
178
488
  }
179
- this.setState({
180
- keyedFormData: newKeyedFormData,
181
- updatedKeyedFormData: true,
182
- },
183
- // add click will pass the empty `path` array to the onChange which adds the appropriate path
184
- () => onChange(keyedToPlainFormData(newKeyedFormData), fieldPathId.path, newErrorSchema));
185
- }
186
- /** Callback handler for when the user clicks on the add button. Creates a new row of keyed form data at the end of
187
- * the list, adding it into the state, and then returning `onChange()` with the plain form data converted from the
188
- * keyed data
189
- *
190
- * @param event - The event for the click
191
- */
192
- onAddClick = (event) => {
193
- this._handleAddClick(event);
194
- };
195
- /** Callback handler for when the user clicks on the add button on an existing array element. Creates a new row of
196
- * keyed form data inserted at the `index`, adding it into the state, and then returning `onChange()` with the plain
197
- * form data converted from the keyed data
198
- *
199
- * @param index - The index at which the add button is clicked
200
- */
201
- onAddIndexClick = (index) => {
202
- return (event) => {
203
- this._handleAddClick(event, index);
204
- };
205
- };
489
+ onChange(updateKeyedFormData(newKeyedFormData), fieldPathId.path, newErrorSchema);
490
+ }, [keyedFormData, registry, schema, onChange, updateKeyedFormData, errorSchema, fieldPathId]);
206
491
  /** Callback handler for when the user clicks on the copy button on an existing array element. Clones the row of
207
492
  * keyed form data at the `index` into the next position in the state, and then returning `onChange()` with the plain
208
493
  * form data converted from the keyed data
209
494
  *
210
495
  * @param index - The index at which the copy button is clicked
211
496
  */
212
- onCopyIndexClick = (index) => {
213
- return (event) => {
214
- if (event) {
215
- event.preventDefault();
216
- }
217
- const { onChange, errorSchema, fieldPathId } = this.props;
218
- const { keyedFormData } = this.state;
219
- // refs #195: revalidate to ensure properly reindexing errors
220
- let newErrorSchema;
221
- if (errorSchema) {
222
- newErrorSchema = {};
223
- for (const idx in errorSchema) {
224
- const i = parseInt(idx);
225
- if (i <= index) {
226
- set(newErrorSchema, [i], errorSchema[idx]);
227
- }
228
- else if (i > index) {
229
- set(newErrorSchema, [i + 1], errorSchema[idx]);
230
- }
497
+ const handleCopyItem = useCallback((event, index) => {
498
+ if (event) {
499
+ event.preventDefault();
500
+ }
501
+ let newErrorSchema;
502
+ if (errorSchema) {
503
+ newErrorSchema = {};
504
+ for (const idx in errorSchema) {
505
+ const i = parseInt(idx);
506
+ if (i <= index) {
507
+ set(newErrorSchema, [i], errorSchema[idx]);
508
+ }
509
+ else if (i > index) {
510
+ set(newErrorSchema, [i + 1], errorSchema[idx]);
231
511
  }
232
512
  }
233
- const newKeyedFormDataRow = {
234
- key: generateRowId(),
235
- item: cloneDeep(keyedFormData[index].item),
236
- };
237
- const newKeyedFormData = [...keyedFormData];
238
- if (index !== undefined) {
239
- newKeyedFormData.splice(index + 1, 0, newKeyedFormDataRow);
240
- }
241
- else {
242
- newKeyedFormData.push(newKeyedFormDataRow);
243
- }
244
- this.setState({
245
- keyedFormData: newKeyedFormData,
246
- updatedKeyedFormData: true,
247
- }, () => onChange(keyedToPlainFormData(newKeyedFormData), fieldPathId.path, newErrorSchema));
513
+ }
514
+ const newKeyedFormDataRow = {
515
+ key: generateRowId(),
516
+ item: cloneDeep(keyedFormData[index].item),
248
517
  };
249
- };
518
+ const newKeyedFormData = [...keyedFormData];
519
+ if (index !== undefined) {
520
+ newKeyedFormData.splice(index + 1, 0, newKeyedFormDataRow);
521
+ }
522
+ else {
523
+ newKeyedFormData.push(newKeyedFormDataRow);
524
+ }
525
+ onChange(updateKeyedFormData(newKeyedFormData), fieldPathId.path, newErrorSchema);
526
+ }, [keyedFormData, onChange, updateKeyedFormData, errorSchema, fieldPathId]);
250
527
  /** Callback handler for when the user clicks on the remove button on an existing array element. Removes the row of
251
528
  * keyed form data at the `index` in the state, and then returning `onChange()` with the plain form data converted
252
529
  * from the keyed data
253
530
  *
254
531
  * @param index - The index at which the remove button is clicked
255
532
  */
256
- onDropIndexClick = (index) => {
257
- return (event) => {
258
- if (event) {
259
- event.preventDefault();
260
- }
261
- const { onChange, errorSchema, fieldPathId } = this.props;
262
- const { keyedFormData } = this.state;
263
- // refs #195: revalidate to ensure properly reindexing errors
264
- let newErrorSchema;
265
- if (errorSchema) {
266
- newErrorSchema = {};
267
- for (const idx in errorSchema) {
268
- const i = parseInt(idx);
269
- if (i < index) {
270
- set(newErrorSchema, [i], errorSchema[idx]);
271
- }
272
- else if (i > index) {
273
- set(newErrorSchema, [i - 1], errorSchema[idx]);
274
- }
533
+ const handleRemoveItem = useCallback((event, index) => {
534
+ if (event) {
535
+ event.preventDefault();
536
+ }
537
+ // refs #195: revalidate to ensure properly reindexing errors
538
+ let newErrorSchema;
539
+ if (errorSchema) {
540
+ newErrorSchema = {};
541
+ for (const idx in errorSchema) {
542
+ const i = parseInt(idx);
543
+ if (i < index) {
544
+ set(newErrorSchema, [i], errorSchema[idx]);
545
+ }
546
+ else if (i > index) {
547
+ set(newErrorSchema, [i - 1], errorSchema[idx]);
275
548
  }
276
549
  }
277
- const newKeyedFormData = keyedFormData.filter((_, i) => i !== index);
278
- this.setState({
279
- keyedFormData: newKeyedFormData,
280
- updatedKeyedFormData: true,
281
- }, () => onChange(keyedToPlainFormData(newKeyedFormData), fieldPathId.path, newErrorSchema));
282
- };
283
- };
550
+ }
551
+ const newKeyedFormData = keyedFormData.filter((_, i) => i !== index);
552
+ onChange(updateKeyedFormData(newKeyedFormData), fieldPathId.path, newErrorSchema);
553
+ }, [keyedFormData, onChange, updateKeyedFormData, errorSchema, fieldPathId]);
284
554
  /** Callback handler for when the user clicks on one of the move item buttons on an existing array element. Moves the
285
555
  * row of keyed form data at the `index` to the `newIndex` in the state, and then returning `onChange()` with the
286
556
  * plain form data converted from the keyed data
@@ -288,366 +558,78 @@ class ArrayField extends Component {
288
558
  * @param index - The index of the item to move
289
559
  * @param newIndex - The index to where the item is to be moved
290
560
  */
291
- onReorderClick = (index, newIndex) => {
292
- return (event) => {
293
- if (event) {
294
- event.preventDefault();
295
- event.currentTarget.blur();
296
- }
297
- const { onChange, errorSchema, fieldPathId } = this.props;
298
- let newErrorSchema;
299
- if (errorSchema) {
300
- newErrorSchema = {};
301
- for (const idx in errorSchema) {
302
- const i = parseInt(idx);
303
- if (i == index) {
304
- set(newErrorSchema, [newIndex], errorSchema[index]);
305
- }
306
- else if (i == newIndex) {
307
- set(newErrorSchema, [index], errorSchema[newIndex]);
308
- }
309
- else {
310
- set(newErrorSchema, [idx], errorSchema[i]);
311
- }
561
+ const handleReorderItems = useCallback((event, index, newIndex) => {
562
+ if (event) {
563
+ event.preventDefault();
564
+ event.currentTarget.blur();
565
+ }
566
+ let newErrorSchema;
567
+ if (errorSchema) {
568
+ newErrorSchema = {};
569
+ for (const idx in errorSchema) {
570
+ const i = parseInt(idx);
571
+ if (i == index) {
572
+ set(newErrorSchema, [newIndex], errorSchema[index]);
573
+ }
574
+ else if (i == newIndex) {
575
+ set(newErrorSchema, [index], errorSchema[newIndex]);
576
+ }
577
+ else {
578
+ set(newErrorSchema, [idx], errorSchema[i]);
312
579
  }
313
580
  }
314
- const { keyedFormData } = this.state;
315
- function reOrderArray() {
316
- // Copy item
317
- const _newKeyedFormData = keyedFormData.slice();
318
- // Moves item from index to newIndex
319
- _newKeyedFormData.splice(index, 1);
320
- _newKeyedFormData.splice(newIndex, 0, keyedFormData[index]);
321
- return _newKeyedFormData;
322
- }
323
- const newKeyedFormData = reOrderArray();
324
- this.setState({
325
- keyedFormData: newKeyedFormData,
326
- }, () => onChange(keyedToPlainFormData(newKeyedFormData), fieldPathId.path, newErrorSchema));
327
- };
328
- };
581
+ }
582
+ function reOrderArray() {
583
+ // Copy item
584
+ const _newKeyedFormData = keyedFormData.slice();
585
+ // Moves item from index to newIndex
586
+ _newKeyedFormData.splice(index, 1);
587
+ _newKeyedFormData.splice(newIndex, 0, keyedFormData[index]);
588
+ return _newKeyedFormData;
589
+ }
590
+ const newKeyedFormData = reOrderArray();
591
+ onChange(updateKeyedFormData(newKeyedFormData), fieldPathId.path, newErrorSchema);
592
+ }, [keyedFormData, onChange, updateKeyedFormData, errorSchema, fieldPathId]);
329
593
  /** Callback handler used to deal with changing the value of the data in the array at the `index`. Calls the
330
594
  * `onChange` callback with the updated form data
331
595
  *
332
596
  * @param index - The index of the item being changed
333
597
  */
334
- onChangeForIndex = (index) => {
335
- return (value, path, newErrorSchema, id) => {
336
- const { onChange } = this.props;
337
- onChange(
338
- // We need to treat undefined items as nulls to have validation.
339
- // See https://github.com/tdegrunt/jsonschema/issues/206
340
- value === undefined ? null : value, path, newErrorSchema, id);
341
- };
342
- };
598
+ const handleChange = useCallback((value, path, newErrorSchema, id) => {
599
+ onChange(
600
+ // We need to treat undefined items as nulls to have validation.
601
+ // See https://github.com/tdegrunt/jsonschema/issues/206
602
+ value === undefined ? null : value, path, newErrorSchema, id);
603
+ }, [onChange]);
343
604
  /** Callback handler used to change the value for a checkbox */
344
- onSelectChange = (value) => {
345
- const { onChange, fieldPathId } = this.props;
346
- // select change will pass an empty `path` array since the `ObjectField` will add the path value automatically
347
- onChange(value, fieldPathId.path, undefined, fieldPathId && fieldPathId.$id);
348
- };
349
- /** Helper method to compute item UI schema for both normal and fixed arrays
350
- * Handles both static object and dynamic function cases
351
- *
352
- * @param uiSchema - The parent UI schema containing items definition
353
- * @param item - The item data
354
- * @param index - The index of the item
355
- * @param formContext - The form context
356
- * @returns The computed UI schema for the item
357
- */
358
- computeItemUiSchema(uiSchema, item, index, formContext) {
359
- if (typeof uiSchema.items === 'function') {
360
- try {
361
- // Call the function with item data, index, and form context
362
- // TypeScript now correctly infers the types thanks to the ArrayElement type in UiSchema
363
- const result = uiSchema.items(item, index, formContext);
364
- // Only use the result if it's truthy
365
- return result;
366
- }
367
- catch (e) {
368
- console.error(`Error executing dynamic uiSchema.items function for item at index ${index}:`, e);
369
- // Fall back to undefined to allow the field to still render
370
- return undefined;
371
- }
372
- }
373
- else {
374
- // Static object case - preserve undefined to maintain backward compatibility
375
- return uiSchema.items;
376
- }
377
- }
378
- /** Renders the `ArrayField` depending on the specific needs of the schema and uischema elements
379
- */
380
- render() {
381
- const { schema, uiSchema, fieldPathId, registry } = this.props;
382
- const { schemaUtils, translateString } = registry;
383
- if (!(ITEMS_KEY in schema)) {
384
- const uiOptions = getUiOptions(uiSchema);
385
- const UnsupportedFieldTemplate = getTemplate('UnsupportedFieldTemplate', registry, uiOptions);
386
- return (_jsx(UnsupportedFieldTemplate, { schema: schema, fieldPathId: fieldPathId, reason: translateString(TranslatableString.MissingItems), registry: registry }));
387
- }
388
- if (schemaUtils.isMultiSelect(schema)) {
389
- // If array has enum or uniqueItems set to true, call renderMultiSelect() to render the default multiselect widget or a custom widget, if specified.
390
- return this.renderMultiSelect();
391
- }
392
- if (isCustomWidget(uiSchema)) {
393
- return this.renderCustomWidget();
394
- }
395
- if (isFixedItems(schema)) {
396
- return this.renderFixedArray();
397
- }
398
- if (schemaUtils.isFilesArray(schema, uiSchema)) {
399
- return this.renderFiles();
400
- }
401
- return this.renderNormalArray();
402
- }
403
- /** Renders a normal array without any limitations of length
404
- */
405
- renderNormalArray() {
406
- const { schema, uiSchema = {}, errorSchema, fieldPathId, name, title, disabled = false, readonly = false, autofocus = false, required = false, registry, onBlur, onFocus, rawErrors, } = this.props;
407
- const { keyedFormData } = this.state;
408
- const fieldTitle = schema.title || title || name;
409
- const { schemaUtils, fields, formContext, globalFormOptions } = registry;
410
- const { OptionalDataControlsField } = fields;
605
+ const onSelectChange = useCallback((value) => {
606
+ onChange(value, fieldPathId.path, undefined, fieldPathId?.[ID_KEY]);
607
+ }, [onChange, fieldPathId]);
608
+ if (!(ITEMS_KEY in schema)) {
411
609
  const uiOptions = getUiOptions(uiSchema);
412
- const _schemaItems = isObject(schema.items) ? schema.items : {};
413
- const itemsSchema = schemaUtils.retrieveSchema(_schemaItems);
414
- const formData = keyedToPlainFormData(this.state.keyedFormData);
415
- const renderOptionalField = shouldRenderOptionalField(registry, schema, required, uiSchema);
416
- const hasFormData = isFormDataAvailable(this.props.formData);
417
- const canAdd = this.canAddItem(formData) && (!renderOptionalField || hasFormData);
418
- const actualFormData = hasFormData ? keyedFormData : [];
419
- const extraClass = renderOptionalField ? ' rjsf-optional-array-field' : '';
420
- const optionalDataControl = renderOptionalField ? _jsx(OptionalDataControlsField, { ...this.props }) : undefined;
421
- const arrayProps = {
422
- canAdd,
423
- items: actualFormData.map((keyedItem, index) => {
424
- const { key, item } = keyedItem;
425
- // While we are actually dealing with a single item of type T, the types require a T[], so cast
426
- const itemCast = item;
427
- const itemSchema = schemaUtils.retrieveSchema(_schemaItems, itemCast);
428
- const itemErrorSchema = errorSchema ? errorSchema[index] : undefined;
429
- const itemFieldPathId = toFieldPathId(index, globalFormOptions, fieldPathId);
430
- // Compute the item UI schema using the helper method
431
- const itemUiSchema = this.computeItemUiSchema(uiSchema, item, index, formContext);
432
- return this.renderArrayFieldItem({
433
- key,
434
- index,
435
- name: name && `${name}-${index}`,
436
- title: fieldTitle ? `${fieldTitle}-${index + 1}` : undefined,
437
- canAdd,
438
- canMoveUp: index > 0,
439
- canMoveDown: index < formData.length - 1,
440
- itemSchema,
441
- itemFieldPathId,
442
- itemErrorSchema,
443
- itemData: itemCast,
444
- itemUiSchema,
445
- autofocus: autofocus && index === 0,
446
- onBlur,
447
- onFocus,
448
- rawErrors,
449
- totalItems: keyedFormData.length,
450
- });
451
- }),
452
- className: `rjsf-field rjsf-field-array rjsf-field-array-of-${itemsSchema.type}${extraClass}`,
453
- disabled,
454
- fieldPathId,
455
- uiSchema,
456
- onAddClick: this.onAddClick,
457
- readonly,
458
- required,
459
- schema,
460
- title: fieldTitle,
461
- formData,
462
- rawErrors,
463
- registry,
464
- optionalDataControl,
465
- };
466
- const Template = getTemplate('ArrayFieldTemplate', registry, uiOptions);
467
- return _jsx(Template, { ...arrayProps });
468
- }
469
- /** Renders an array using the custom widget provided by the user in the `uiSchema`
470
- */
471
- renderCustomWidget() {
472
- const { schema, fieldPathId, uiSchema, disabled = false, readonly = false, autofocus = false, required = false, hideError, placeholder, onBlur, onFocus, formData: items = [], registry, rawErrors, name, } = this.props;
473
- const { widgets, formContext, globalUiOptions, schemaUtils } = registry;
474
- const { widget, title: uiTitle, ...options } = getUiOptions(uiSchema, globalUiOptions);
475
- const Widget = getWidget(schema, widget, widgets);
476
- const label = uiTitle ?? schema.title ?? name;
477
- const displayLabel = schemaUtils.getDisplayLabel(schema, uiSchema, globalUiOptions);
478
- return (_jsx(Widget, { id: fieldPathId.$id, name: name, multiple: true, onChange: this.onSelectChange, onBlur: onBlur, onFocus: onFocus, options: options, schema: schema, uiSchema: uiSchema, registry: registry, value: items, disabled: disabled, readonly: readonly, hideError: hideError, required: required, label: label, hideLabel: !displayLabel, placeholder: placeholder, formContext: formContext, autofocus: autofocus, rawErrors: rawErrors }));
610
+ const UnsupportedFieldTemplate = getTemplate('UnsupportedFieldTemplate', registry, uiOptions);
611
+ return (_jsx(UnsupportedFieldTemplate, { schema: schema, fieldPathId: fieldPathId, reason: translateString(TranslatableString.MissingItems), registry: registry }));
479
612
  }
480
- /** Renders an array as a set of checkboxes
481
- */
482
- renderMultiSelect() {
483
- const { schema, fieldPathId, uiSchema, formData: items = [], disabled = false, readonly = false, autofocus = false, required = false, placeholder, onBlur, onFocus, registry, rawErrors, name, } = this.props;
484
- const { widgets, schemaUtils, formContext, globalUiOptions } = registry;
485
- const itemsSchema = schemaUtils.retrieveSchema(schema.items, items);
486
- const enumOptions = optionsList(itemsSchema, uiSchema);
487
- const { widget = 'select', title: uiTitle, ...options } = getUiOptions(uiSchema, globalUiOptions);
488
- const Widget = getWidget(schema, widget, widgets);
489
- const label = uiTitle ?? schema.title ?? name;
490
- const displayLabel = schemaUtils.getDisplayLabel(schema, uiSchema, globalUiOptions);
491
- return (_jsx(Widget, { id: fieldPathId.$id, name: name, multiple: true, onChange: this.onSelectChange, onBlur: onBlur, onFocus: onFocus, options: { ...options, enumOptions }, schema: schema, uiSchema: uiSchema, registry: registry, value: items, disabled: disabled, readonly: readonly, required: required, label: label, hideLabel: !displayLabel, placeholder: placeholder, formContext: formContext, autofocus: autofocus, rawErrors: rawErrors }));
613
+ const arrayProps = {
614
+ handleAddItem,
615
+ handleCopyItem,
616
+ handleRemoveItem,
617
+ handleReorderItems,
618
+ keyedFormData,
619
+ onChange: handleChange,
620
+ };
621
+ if (schemaUtils.isMultiSelect(schema)) {
622
+ // If array has enum or uniqueItems set to true, call renderMultiSelect() to render the default multiselect widget or a custom widget, if specified.
623
+ return _jsx(ArrayAsMultiSelect, { ...props, onSelectChange: onSelectChange });
492
624
  }
493
- /** Renders an array of files using the `FileWidget`
494
- */
495
- renderFiles() {
496
- const { schema, uiSchema, fieldPathId, name, disabled = false, readonly = false, autofocus = false, required = false, onBlur, onFocus, registry, formData: items = [], rawErrors, } = this.props;
497
- const { widgets, formContext, globalUiOptions, schemaUtils } = registry;
498
- const { widget = 'files', title: uiTitle, ...options } = getUiOptions(uiSchema, globalUiOptions);
499
- const Widget = getWidget(schema, widget, widgets);
500
- const label = uiTitle ?? schema.title ?? name;
501
- const displayLabel = schemaUtils.getDisplayLabel(schema, uiSchema, globalUiOptions);
502
- return (_jsx(Widget, { options: options, id: fieldPathId.$id, name: name, multiple: true, onChange: this.onSelectChange, onBlur: onBlur, onFocus: onFocus, schema: schema, uiSchema: uiSchema, value: items, disabled: disabled, readonly: readonly, required: required, registry: registry, formContext: formContext, autofocus: autofocus, rawErrors: rawErrors, label: label, hideLabel: !displayLabel }));
625
+ if (isCustomWidget(uiSchema)) {
626
+ return _jsx(ArrayAsCustomWidget, { ...props, onSelectChange: onSelectChange });
503
627
  }
504
- /** Renders an array that has a maximum limit of items
505
- */
506
- renderFixedArray() {
507
- const { schema, uiSchema = {}, formData, errorSchema, fieldPathId, name, title, disabled = false, readonly = false, autofocus = false, required = false, registry, onBlur, onFocus, rawErrors, } = this.props;
508
- let { formData: items = [] } = this.props;
509
- const { keyedFormData } = this.state;
510
- const fieldTitle = schema.title || title || name;
511
- const uiOptions = getUiOptions(uiSchema);
512
- const { schemaUtils, fields, formContext, globalFormOptions } = registry;
513
- const { OptionalDataControlsField } = fields;
514
- const renderOptionalField = shouldRenderOptionalField(registry, schema, required, uiSchema);
515
- const hasFormData = isFormDataAvailable(formData);
516
- const _schemaItems = isObject(schema.items) ? schema.items : [];
517
- const itemSchemas = _schemaItems.map((item, index) => schemaUtils.retrieveSchema(item, items[index]));
518
- const additionalSchema = isObject(schema.additionalItems)
519
- ? schemaUtils.retrieveSchema(schema.additionalItems, formData)
520
- : null;
521
- if (items.length < itemSchemas.length) {
522
- // to make sure at least all fixed items are generated
523
- items = items.concat(new Array(itemSchemas.length - items.length));
524
- }
525
- const actualFormData = hasFormData ? keyedFormData : [];
526
- const extraClass = renderOptionalField ? ' rjsf-optional-array-field' : '';
527
- const optionalDataControl = renderOptionalField ? _jsx(OptionalDataControlsField, { ...this.props }) : undefined;
528
- // These are the props passed into the render function
529
- const canAdd = this.canAddItem(items) && !!additionalSchema && (!renderOptionalField || hasFormData);
530
- const arrayProps = {
531
- canAdd,
532
- className: `rjsf-field rjsf-field-array rjsf-field-array-fixed-items${extraClass}`,
533
- disabled,
534
- fieldPathId,
535
- formData,
536
- items: actualFormData.map((keyedItem, index) => {
537
- const { key, item } = keyedItem;
538
- // While we are actually dealing with a single item of type T, the types require a T[], so cast
539
- const itemCast = item;
540
- const additional = index >= itemSchemas.length;
541
- const itemSchema = (additional && isObject(schema.additionalItems)
542
- ? schemaUtils.retrieveSchema(schema.additionalItems, itemCast)
543
- : itemSchemas[index]) || {};
544
- const itemFieldPathId = toFieldPathId(index, globalFormOptions, fieldPathId);
545
- // Compute the item UI schema - handle both static and dynamic cases
546
- let itemUiSchema;
547
- if (additional) {
548
- // For additional items, use additionalItems uiSchema
549
- itemUiSchema = uiSchema.additionalItems;
550
- }
551
- else {
552
- // For fixed items, uiSchema.items can be an array, a function, or a single object
553
- if (Array.isArray(uiSchema.items)) {
554
- itemUiSchema = uiSchema.items[index];
555
- }
556
- else {
557
- // Use the helper method for function or static object cases
558
- itemUiSchema = this.computeItemUiSchema(uiSchema, item, index, formContext);
559
- }
560
- }
561
- const itemErrorSchema = errorSchema ? errorSchema[index] : undefined;
562
- return this.renderArrayFieldItem({
563
- key,
564
- index,
565
- name: name && `${name}-${index}`,
566
- title: fieldTitle ? `${fieldTitle}-${index + 1}` : undefined,
567
- canAdd,
568
- canRemove: additional,
569
- canMoveUp: index >= itemSchemas.length + 1,
570
- canMoveDown: additional && index < items.length - 1,
571
- itemSchema,
572
- itemData: itemCast,
573
- itemUiSchema,
574
- itemFieldPathId,
575
- itemErrorSchema,
576
- autofocus: autofocus && index === 0,
577
- onBlur,
578
- onFocus,
579
- rawErrors,
580
- totalItems: keyedFormData.length,
581
- });
582
- }),
583
- onAddClick: this.onAddClick,
584
- readonly,
585
- required,
586
- registry,
587
- schema,
588
- uiSchema,
589
- title: fieldTitle,
590
- errorSchema,
591
- rawErrors,
592
- optionalDataControl,
593
- };
594
- const Template = getTemplate('ArrayFieldTemplate', registry, uiOptions);
595
- return _jsx(Template, { ...arrayProps });
628
+ if (isFixedItems(schema)) {
629
+ return _jsx(FixedArray, { ...props, ...arrayProps });
596
630
  }
597
- /** Renders the individual array item using a `SchemaField` along with the additional properties required to be send
598
- * back to the `ArrayFieldItemTemplate`.
599
- *
600
- * @param props - The props for the individual array item to be rendered
601
- */
602
- renderArrayFieldItem(props) {
603
- const { key, index, name, canAdd, canRemove = true, canMoveUp, canMoveDown, itemSchema, itemData, itemUiSchema, itemFieldPathId, itemErrorSchema, autofocus, onBlur, onFocus, rawErrors, totalItems, title, } = props;
604
- const { disabled, hideError, readonly, uiSchema, registry, formContext } = this.props;
605
- const { fields: { ArraySchemaField, SchemaField }, globalUiOptions, } = registry;
606
- const ItemSchemaField = ArraySchemaField || SchemaField;
607
- const { orderable = true, removable = true, copyable = false } = getUiOptions(uiSchema, globalUiOptions);
608
- const has = {
609
- moveUp: orderable && canMoveUp,
610
- moveDown: orderable && canMoveDown,
611
- copy: copyable && canAdd,
612
- remove: removable && canRemove,
613
- toolbar: false,
614
- };
615
- has.toolbar = Object.keys(has).some((key) => has[key]);
616
- return {
617
- children: (_jsx(ItemSchemaField, { name: name, title: title, index: index, schema: itemSchema, uiSchema: itemUiSchema, formData: itemData, formContext: formContext, errorSchema: itemErrorSchema, fieldPathId: itemFieldPathId, required: this.isItemRequired(itemSchema), onChange: this.onChangeForIndex(index), onBlur: onBlur, onFocus: onFocus, registry: registry, disabled: disabled, readonly: readonly, hideError: hideError, autofocus: autofocus, rawErrors: rawErrors })),
618
- buttonsProps: {
619
- fieldPathId: itemFieldPathId,
620
- disabled: disabled,
621
- readonly: readonly,
622
- canAdd,
623
- hasCopy: has.copy,
624
- hasMoveUp: has.moveUp,
625
- hasMoveDown: has.moveDown,
626
- hasRemove: has.remove,
627
- index: index,
628
- totalItems: totalItems,
629
- onAddIndexClick: this.onAddIndexClick,
630
- onCopyIndexClick: this.onCopyIndexClick,
631
- onDropIndexClick: this.onDropIndexClick,
632
- onReorderClick: this.onReorderClick,
633
- registry: registry,
634
- schema: itemSchema,
635
- uiSchema: itemUiSchema,
636
- },
637
- className: 'rjsf-array-item',
638
- disabled,
639
- hasToolbar: has.toolbar,
640
- index,
641
- totalItems,
642
- key,
643
- readonly,
644
- registry,
645
- schema: itemSchema,
646
- uiSchema: itemUiSchema,
647
- };
631
+ if (schemaUtils.isFilesArray(schema, uiSchema)) {
632
+ return _jsx(ArrayAsFiles, { ...props, onSelectChange: onSelectChange });
648
633
  }
634
+ return _jsx(NormalArray, { ...props, ...arrayProps });
649
635
  }
650
- /** `ArrayField` is `React.ComponentType<FieldProps<T[], S, F>>` (necessarily) but the `registry` requires things to be a
651
- * `Field` which is defined as `React.ComponentType<FieldProps<T, S, F>>`, so cast it to make `registry` happy.
652
- */
653
- export default ArrayField;