@rjsf/core 6.0.0-beta.13 → 6.0.0-beta.14
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.
- package/dist/core.umd.js +235 -121
- package/dist/index.esm.js +277 -157
- package/dist/index.esm.js.map +3 -3
- package/dist/index.js +301 -182
- package/dist/index.js.map +3 -3
- package/lib/components/Form.d.ts +43 -12
- package/lib/components/Form.d.ts.map +1 -1
- package/lib/components/Form.js +70 -22
- package/lib/components/fields/ArrayField.d.ts +14 -4
- package/lib/components/fields/ArrayField.d.ts.map +1 -1
- package/lib/components/fields/ArrayField.js +74 -28
- package/lib/components/fields/BooleanField.d.ts.map +1 -1
- package/lib/components/fields/BooleanField.js +6 -1
- package/lib/components/fields/LayoutGridField.d.ts +19 -1
- package/lib/components/fields/LayoutGridField.d.ts.map +1 -1
- package/lib/components/fields/LayoutGridField.js +62 -12
- package/lib/components/fields/LayoutMultiSchemaField.d.ts.map +1 -1
- package/lib/components/fields/LayoutMultiSchemaField.js +2 -1
- package/lib/components/fields/MultiSchemaField.d.ts.map +1 -1
- package/lib/components/fields/MultiSchemaField.js +2 -1
- package/lib/components/fields/NullField.js +3 -3
- package/lib/components/fields/NumberField.js +2 -2
- package/lib/components/fields/ObjectField.d.ts +2 -2
- package/lib/components/fields/ObjectField.d.ts.map +1 -1
- package/lib/components/fields/ObjectField.js +16 -19
- package/lib/components/fields/SchemaField.js +2 -2
- package/lib/components/fields/StringField.d.ts.map +1 -1
- package/lib/components/fields/StringField.js +6 -1
- package/lib/components/widgets/AltDateWidget.d.ts.map +1 -1
- package/lib/components/widgets/AltDateWidget.js +15 -18
- package/lib/components/widgets/CheckboxesWidget.js +2 -2
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +11 -11
- package/src/components/Form.tsx +85 -22
- package/src/components/fields/ArrayField.tsx +75 -29
- package/src/components/fields/BooleanField.tsx +10 -1
- package/src/components/fields/LayoutGridField.tsx +69 -11
- package/src/components/fields/LayoutMultiSchemaField.tsx +2 -1
- package/src/components/fields/MultiSchemaField.tsx +2 -1
- package/src/components/fields/NullField.tsx +3 -3
- package/src/components/fields/NumberField.tsx +2 -2
- package/src/components/fields/ObjectField.tsx +16 -26
- package/src/components/fields/SchemaField.tsx +2 -2
- package/src/components/fields/StringField.tsx +10 -1
- package/src/components/widgets/AltDateWidget.tsx +20 -22
- package/src/components/widgets/CheckboxesWidget.tsx +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rjsf/core",
|
|
3
|
-
"version": "6.0.0-beta.
|
|
3
|
+
"version": "6.0.0-beta.14",
|
|
4
4
|
"description": "A simple React component capable of building HTML forms out of a JSON schema.",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"compileReplacer": "tsc -p tsconfig.replacer.json && move-file lodashReplacer.js lodashReplacer.cjs",
|
|
@@ -66,30 +66,30 @@
|
|
|
66
66
|
"node": ">=20"
|
|
67
67
|
},
|
|
68
68
|
"peerDependencies": {
|
|
69
|
-
"@rjsf/utils": "^6.0.0-beta",
|
|
69
|
+
"@rjsf/utils": "^6.0.0-beta.14",
|
|
70
70
|
"react": ">=18"
|
|
71
71
|
},
|
|
72
72
|
"dependencies": {
|
|
73
73
|
"lodash": "^4.17.21",
|
|
74
74
|
"lodash-es": "^4.17.21",
|
|
75
|
-
"markdown-to-jsx": "^7.7.
|
|
75
|
+
"markdown-to-jsx": "^7.7.13",
|
|
76
76
|
"nanoid": "^5.1.5",
|
|
77
77
|
"prop-types": "^15.8.1"
|
|
78
78
|
},
|
|
79
79
|
"devDependencies": {
|
|
80
|
-
"@rjsf/snapshot-tests": "^6.0.0-beta.
|
|
81
|
-
"@rjsf/utils": "^6.0.0-beta.
|
|
82
|
-
"@rjsf/validator-ajv8": "^6.0.0-beta.
|
|
83
|
-
"@testing-library/jest-dom": "^6.6.
|
|
84
|
-
"@testing-library/react": "^16.
|
|
80
|
+
"@rjsf/snapshot-tests": "^6.0.0-beta.14",
|
|
81
|
+
"@rjsf/utils": "^6.0.0-beta.14",
|
|
82
|
+
"@rjsf/validator-ajv8": "^6.0.0-beta.14",
|
|
83
|
+
"@testing-library/jest-dom": "^6.6.4",
|
|
84
|
+
"@testing-library/react": "^16.3.0",
|
|
85
85
|
"@testing-library/user-event": "^14.6.1",
|
|
86
|
-
"ajv": "^8.
|
|
86
|
+
"ajv": "^8.17.1",
|
|
87
87
|
"atob": "^2.1.2",
|
|
88
88
|
"chai": "^3.5.0",
|
|
89
|
-
"eslint": "^8.
|
|
89
|
+
"eslint": "^8.57.1",
|
|
90
90
|
"html": "^1.0.0",
|
|
91
91
|
"jsdom": "^20.0.3",
|
|
92
|
-
"mocha": "^10.2
|
|
92
|
+
"mocha": "^10.8.2",
|
|
93
93
|
"react-portal": "^4.3.0",
|
|
94
94
|
"sinon": "^9.2.4"
|
|
95
95
|
},
|
package/src/components/Form.tsx
CHANGED
|
@@ -38,11 +38,13 @@ import {
|
|
|
38
38
|
createErrorHandler,
|
|
39
39
|
unwrapErrorHandler,
|
|
40
40
|
} from '@rjsf/utils';
|
|
41
|
+
import _cloneDeep from 'lodash/cloneDeep';
|
|
41
42
|
import _forEach from 'lodash/forEach';
|
|
42
43
|
import _get from 'lodash/get';
|
|
43
44
|
import _isEmpty from 'lodash/isEmpty';
|
|
44
45
|
import _isNil from 'lodash/isNil';
|
|
45
46
|
import _pick from 'lodash/pick';
|
|
47
|
+
import _set from 'lodash/set';
|
|
46
48
|
import _toPath from 'lodash/toPath';
|
|
47
49
|
|
|
48
50
|
import getDefaultRegistry from '../getDefaultRegistry';
|
|
@@ -272,6 +274,19 @@ export interface IChangeEvent<T = any, S extends StrictRJSFSchema = RJSFSchema,
|
|
|
272
274
|
status?: 'submitted';
|
|
273
275
|
}
|
|
274
276
|
|
|
277
|
+
/** The definition of a pending change that will be processed in the `onChange` handler
|
|
278
|
+
*/
|
|
279
|
+
interface PendingChange<T> {
|
|
280
|
+
/** The path into the formData/errorSchema at which the `newValue`/`newErrorSchema` will be set */
|
|
281
|
+
path?: (number | string)[];
|
|
282
|
+
/** The new value to set into the formData */
|
|
283
|
+
newValue?: T;
|
|
284
|
+
/** The new errors to be set into the errorSchema, if any */
|
|
285
|
+
newErrorSchema?: ErrorSchema<T>;
|
|
286
|
+
/** The optional id of the field for which the change is being made */
|
|
287
|
+
id?: string;
|
|
288
|
+
}
|
|
289
|
+
|
|
275
290
|
/** The `Form` component renders the outer form and all the fields defined in the `schema` */
|
|
276
291
|
export default class Form<
|
|
277
292
|
T = any,
|
|
@@ -283,6 +298,10 @@ export default class Form<
|
|
|
283
298
|
*/
|
|
284
299
|
formElement: RefObject<any>;
|
|
285
300
|
|
|
301
|
+
/** The list of pending changes
|
|
302
|
+
*/
|
|
303
|
+
pendingChanges: PendingChange<T>[] = [];
|
|
304
|
+
|
|
286
305
|
/** Constructs the `Form` from the `props`. Will setup the initial state from the props. It will also call the
|
|
287
306
|
* `onChange` handler if the initially provided `formData` is modified to add missing default values as part of the
|
|
288
307
|
* state construction.
|
|
@@ -539,8 +558,7 @@ export default class Form<
|
|
|
539
558
|
let customValidateErrors = {};
|
|
540
559
|
if (typeof customValidate === 'function') {
|
|
541
560
|
const errorHandler = customValidate(prevFormData, createErrorHandler<T>(prevFormData), uiSchema);
|
|
542
|
-
|
|
543
|
-
customValidateErrors = userErrorSchema;
|
|
561
|
+
customValidateErrors = unwrapErrorHandler<T>(errorHandler);
|
|
544
562
|
}
|
|
545
563
|
return customValidateErrors;
|
|
546
564
|
}
|
|
@@ -550,7 +568,8 @@ export default class Form<
|
|
|
550
568
|
*
|
|
551
569
|
* @param formData - The new form data to validate
|
|
552
570
|
* @param schema - The schema used to validate against
|
|
553
|
-
* @param altSchemaUtils - The alternate schemaUtils to use for validation
|
|
571
|
+
* @param [altSchemaUtils] - The alternate schemaUtils to use for validation
|
|
572
|
+
* @param [retrievedSchema] - An optionally retrieved schema for per
|
|
554
573
|
*/
|
|
555
574
|
validate(
|
|
556
575
|
formData: T | undefined,
|
|
@@ -655,11 +674,16 @@ export default class Form<
|
|
|
655
674
|
const retrievedSchema = schemaUtils.retrieveSchema(schema, formData);
|
|
656
675
|
const pathSchema = schemaUtils.toPathSchema(retrievedSchema, '', formData);
|
|
657
676
|
const fieldNames = this.getFieldNames(pathSchema, formData);
|
|
658
|
-
|
|
659
|
-
return newFormData;
|
|
677
|
+
return this.getUsedFormData(formData, fieldNames);
|
|
660
678
|
};
|
|
661
679
|
|
|
662
|
-
|
|
680
|
+
/** Filtering errors based on your retrieved schema to only show errors for properties in the selected branch.
|
|
681
|
+
*
|
|
682
|
+
* @param schemaErrors - The schema errors to filter
|
|
683
|
+
* @param [resolvedSchema] - An optionally resolved schema to use for performance reasons
|
|
684
|
+
* @param [formData] - The formData to help filter errors
|
|
685
|
+
* @private
|
|
686
|
+
*/
|
|
663
687
|
private filterErrorsBasedOnSchema(schemaErrors: ErrorSchema<T>, resolvedSchema?: S, formData?: any): ErrorSchema<T> {
|
|
664
688
|
const { retrievedSchema, schemaUtils } = this.state;
|
|
665
689
|
const _retrievedSchema = resolvedSchema ?? retrievedSchema;
|
|
@@ -705,23 +729,47 @@ export default class Form<
|
|
|
705
729
|
return filterNilOrEmptyErrors(filteredErrors, prevCustomValidateErrors);
|
|
706
730
|
}
|
|
707
731
|
|
|
708
|
-
/**
|
|
709
|
-
*
|
|
710
|
-
* then, if `omitExtraData` and `liveOmit` are turned on, the `formData` will be filtered to remove any extra data not
|
|
711
|
-
* in a form field. Then, the resulting formData will be validated if required. The state will be updated with the new
|
|
712
|
-
* updated (potentially filtered) `formData`, any errors that resulted from validation. Finally the `onChange`
|
|
713
|
-
* callback will be called if specified with the updated state.
|
|
732
|
+
/** Pushes the given change information into the `pendingChanges` array and then calls `processPendingChanges()` if
|
|
733
|
+
* the array only contains a single pending change.
|
|
714
734
|
*
|
|
715
|
-
* @param
|
|
716
|
-
* @param
|
|
717
|
-
* @param
|
|
735
|
+
* @param newValue - The new form data from a change to a field
|
|
736
|
+
* @param [path] - The path to the change into which to set the formData
|
|
737
|
+
* @param [newErrorSchema] - The new `ErrorSchema` based on the field change
|
|
738
|
+
* @param [id] - The id of the field that caused the change
|
|
739
|
+
*/
|
|
740
|
+
onChange = (newValue: T | undefined, path?: (number | string)[], newErrorSchema?: ErrorSchema<T>, id?: string) => {
|
|
741
|
+
this.pendingChanges.push({ newValue, path, newErrorSchema, id });
|
|
742
|
+
if (this.pendingChanges.length === 1) {
|
|
743
|
+
this.processPendingChange();
|
|
744
|
+
}
|
|
745
|
+
};
|
|
746
|
+
|
|
747
|
+
/** Function to handle changes made to a field in the `Form`. This handler gets the first change from the
|
|
748
|
+
* `pendingChanges` list, containing the `newValue` for the `formData` and the `path` at which the `newValue` is to be
|
|
749
|
+
* updated, along with a new, optional `ErrorSchema` for that same `path` and potentially the `id` of the field being
|
|
750
|
+
* changed. It will first update the `formData` with any missing default fields and then, if `omitExtraData` and
|
|
751
|
+
* `liveOmit` are turned on, the `formData` will be filtered to remove any extra data not in a form field. Then, the
|
|
752
|
+
* resulting `formData` will be validated if required. The state will be updated with the new updated (potentially
|
|
753
|
+
* filtered) `formData`, any errors that resulted from validation. Finally the `onChange` callback will be called, if
|
|
754
|
+
* specified, with the updated state and the `processPendingChange()` function is called again.
|
|
718
755
|
*/
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
756
|
+
processPendingChange() {
|
|
757
|
+
if (this.pendingChanges.length === 0) {
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
const { newValue, path, id } = this.pendingChanges[0];
|
|
761
|
+
let { newErrorSchema } = this.pendingChanges[0];
|
|
762
|
+
const { extraErrors, omitExtraData, liveOmit, noValidate, liveValidate, onChange, idPrefix = '' } = this.props;
|
|
763
|
+
const { formData: oldFormData, schemaUtils, schema, errorSchema } = this.state;
|
|
722
764
|
|
|
765
|
+
const isRootPath = !path || path.length === 0 || (path.length === 1 && path[0] === idPrefix);
|
|
723
766
|
let retrievedSchema = this.state.retrievedSchema;
|
|
767
|
+
let formData = isRootPath ? newValue : _cloneDeep(oldFormData);
|
|
724
768
|
if (isObject(formData) || Array.isArray(formData)) {
|
|
769
|
+
if (!isRootPath) {
|
|
770
|
+
// If the newValue is not on the root path, then set it into the form data
|
|
771
|
+
_set(formData, path, newValue);
|
|
772
|
+
}
|
|
725
773
|
const newState = this.getStateFromProps(this.props, formData);
|
|
726
774
|
formData = newState.formData;
|
|
727
775
|
retrievedSchema = newState.retrievedSchema;
|
|
@@ -738,6 +786,13 @@ export default class Form<
|
|
|
738
786
|
};
|
|
739
787
|
}
|
|
740
788
|
|
|
789
|
+
// First update the value in the newErrorSchema in a copy of the old error schema if it was specified and the path
|
|
790
|
+
// is not the root
|
|
791
|
+
if (newErrorSchema && !isRootPath) {
|
|
792
|
+
const errorSchemaCopy = _cloneDeep(errorSchema);
|
|
793
|
+
_set(errorSchemaCopy, path, newErrorSchema);
|
|
794
|
+
newErrorSchema = errorSchemaCopy;
|
|
795
|
+
}
|
|
741
796
|
if (mustValidate) {
|
|
742
797
|
const schemaValidation = this.validate(newFormData, schema, schemaUtils, retrievedSchema);
|
|
743
798
|
let errors = schemaValidation.errors;
|
|
@@ -762,6 +817,7 @@ export default class Form<
|
|
|
762
817
|
schemaValidationErrorSchema,
|
|
763
818
|
};
|
|
764
819
|
} else if (!noValidate && newErrorSchema) {
|
|
820
|
+
// Merging 'newErrorSchema' into 'errorSchema' to display the custom raised errors.
|
|
765
821
|
const errorSchema = extraErrors
|
|
766
822
|
? (mergeObjects(newErrorSchema, extraErrors, 'preventDuplicates') as ErrorSchema<T>)
|
|
767
823
|
: newErrorSchema;
|
|
@@ -771,8 +827,15 @@ export default class Form<
|
|
|
771
827
|
errors: toErrorList(errorSchema),
|
|
772
828
|
};
|
|
773
829
|
}
|
|
774
|
-
this.setState(state as FormState<T, S, F>, () =>
|
|
775
|
-
|
|
830
|
+
this.setState(state as FormState<T, S, F>, () => {
|
|
831
|
+
if (onChange) {
|
|
832
|
+
onChange({ ...this.state, ...state }, id);
|
|
833
|
+
}
|
|
834
|
+
// Now remove the change we just completed and call this again
|
|
835
|
+
this.pendingChanges.shift();
|
|
836
|
+
this.processPendingChange();
|
|
837
|
+
});
|
|
838
|
+
}
|
|
776
839
|
|
|
777
840
|
/**
|
|
778
841
|
* If the retrievedSchema has changed the new retrievedSchema is returned.
|
|
@@ -1029,7 +1092,7 @@ export default class Form<
|
|
|
1029
1092
|
const {
|
|
1030
1093
|
children,
|
|
1031
1094
|
id,
|
|
1032
|
-
idPrefix,
|
|
1095
|
+
idPrefix = '',
|
|
1033
1096
|
idSeparator,
|
|
1034
1097
|
className = '',
|
|
1035
1098
|
tagName,
|
|
@@ -1082,7 +1145,7 @@ export default class Form<
|
|
|
1082
1145
|
>
|
|
1083
1146
|
{showErrorList === 'top' && this.renderErrors(registry)}
|
|
1084
1147
|
<_SchemaField
|
|
1085
|
-
name=
|
|
1148
|
+
name={idPrefix}
|
|
1086
1149
|
schema={schema}
|
|
1087
1150
|
uiSchema={uiSchema}
|
|
1088
1151
|
errorSchema={errorSchema}
|
|
@@ -22,7 +22,7 @@ import cloneDeep from 'lodash/cloneDeep';
|
|
|
22
22
|
import get from 'lodash/get';
|
|
23
23
|
import isObject from 'lodash/isObject';
|
|
24
24
|
import set from 'lodash/set';
|
|
25
|
-
import
|
|
25
|
+
import uniqueId from 'lodash/uniqueId';
|
|
26
26
|
|
|
27
27
|
/** Type used to represent the keyed form data used in the state */
|
|
28
28
|
type KeyedFormDataType<T> = { key: string; item: T };
|
|
@@ -37,7 +37,7 @@ type ArrayFieldState<T> = {
|
|
|
37
37
|
|
|
38
38
|
/** Used to generate a unique ID for an element in a row */
|
|
39
39
|
function generateRowId() {
|
|
40
|
-
return
|
|
40
|
+
return uniqueId('rjsf-array-item-');
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
/** Converts the `formData` into `KeyedFormDataType` data, using the `generateRowId()` function to create the key
|
|
@@ -229,7 +229,8 @@ class ArrayField<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends For
|
|
|
229
229
|
keyedFormData: newKeyedFormData,
|
|
230
230
|
updatedKeyedFormData: true,
|
|
231
231
|
},
|
|
232
|
-
|
|
232
|
+
// add click will pass the empty `path` array to the onChange which adds the appropriate path
|
|
233
|
+
() => onChange(keyedToPlainFormData(newKeyedFormData), [], newErrorSchema as ErrorSchema<T[]>),
|
|
233
234
|
);
|
|
234
235
|
}
|
|
235
236
|
|
|
@@ -298,7 +299,8 @@ class ArrayField<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends For
|
|
|
298
299
|
keyedFormData: newKeyedFormData,
|
|
299
300
|
updatedKeyedFormData: true,
|
|
300
301
|
},
|
|
301
|
-
|
|
302
|
+
// Copy index will pass the empty `path` array to the onChange which adds the appropriate path
|
|
303
|
+
() => onChange(keyedToPlainFormData(newKeyedFormData), [], newErrorSchema as ErrorSchema<T[]>),
|
|
302
304
|
);
|
|
303
305
|
};
|
|
304
306
|
};
|
|
@@ -335,7 +337,8 @@ class ArrayField<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends For
|
|
|
335
337
|
keyedFormData: newKeyedFormData,
|
|
336
338
|
updatedKeyedFormData: true,
|
|
337
339
|
},
|
|
338
|
-
|
|
340
|
+
// drop index will pass the empty `path` array to the onChange which adds the appropriate path
|
|
341
|
+
() => onChange(keyedToPlainFormData(newKeyedFormData), [], newErrorSchema as ErrorSchema<T[]>),
|
|
339
342
|
);
|
|
340
343
|
};
|
|
341
344
|
};
|
|
@@ -385,7 +388,8 @@ class ArrayField<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends For
|
|
|
385
388
|
{
|
|
386
389
|
keyedFormData: newKeyedFormData,
|
|
387
390
|
},
|
|
388
|
-
|
|
391
|
+
// reorder click will pass the empty `path` array to the onChange which adds the appropriate path
|
|
392
|
+
() => onChange(keyedToPlainFormData(newKeyedFormData), [], newErrorSchema as ErrorSchema<T[]>),
|
|
389
393
|
);
|
|
390
394
|
};
|
|
391
395
|
};
|
|
@@ -396,22 +400,17 @@ class ArrayField<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends For
|
|
|
396
400
|
* @param index - The index of the item being changed
|
|
397
401
|
*/
|
|
398
402
|
onChangeForIndex = (index: number) => {
|
|
399
|
-
return (value: any, newErrorSchema?: ErrorSchema<T>, id?: string) => {
|
|
400
|
-
const {
|
|
401
|
-
|
|
402
|
-
const
|
|
403
|
+
return (value: any, path?: (number | string)[], newErrorSchema?: ErrorSchema<T>, id?: string) => {
|
|
404
|
+
const { onChange } = this.props;
|
|
405
|
+
// Copy the current path and insert in the index into the first location
|
|
406
|
+
const changePath = Array.isArray(path) ? path.slice() : [];
|
|
407
|
+
changePath.unshift(index);
|
|
408
|
+
onChange(
|
|
403
409
|
// We need to treat undefined items as nulls to have validation.
|
|
404
410
|
// See https://github.com/tdegrunt/jsonschema/issues/206
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
onChange(
|
|
409
|
-
newFormData,
|
|
410
|
-
errorSchema &&
|
|
411
|
-
errorSchema && {
|
|
412
|
-
...errorSchema,
|
|
413
|
-
[index]: newErrorSchema,
|
|
414
|
-
},
|
|
411
|
+
value === undefined ? null : value,
|
|
412
|
+
changePath,
|
|
413
|
+
newErrorSchema as ErrorSchema<T[]>,
|
|
415
414
|
id,
|
|
416
415
|
);
|
|
417
416
|
};
|
|
@@ -419,10 +418,44 @@ class ArrayField<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends For
|
|
|
419
418
|
|
|
420
419
|
/** Callback handler used to change the value for a checkbox */
|
|
421
420
|
onSelectChange = (value: any) => {
|
|
422
|
-
const { onChange, idSchema } = this.props;
|
|
423
|
-
|
|
421
|
+
const { name, onChange, idSchema } = this.props;
|
|
422
|
+
// select change will pass the `path` array with the name
|
|
423
|
+
onChange(value, [name], undefined, idSchema && idSchema.$id);
|
|
424
424
|
};
|
|
425
425
|
|
|
426
|
+
/** Helper method to compute item UI schema for both normal and fixed arrays
|
|
427
|
+
* Handles both static object and dynamic function cases
|
|
428
|
+
*
|
|
429
|
+
* @param uiSchema - The parent UI schema containing items definition
|
|
430
|
+
* @param item - The item data
|
|
431
|
+
* @param index - The index of the item
|
|
432
|
+
* @param formContext - The form context
|
|
433
|
+
* @returns The computed UI schema for the item
|
|
434
|
+
*/
|
|
435
|
+
private computeItemUiSchema(
|
|
436
|
+
uiSchema: UiSchema<T[], S, F>,
|
|
437
|
+
item: T,
|
|
438
|
+
index: number,
|
|
439
|
+
formContext: F,
|
|
440
|
+
): UiSchema<T[], S, F> | undefined {
|
|
441
|
+
if (typeof uiSchema.items === 'function') {
|
|
442
|
+
try {
|
|
443
|
+
// Call the function with item data, index, and form context
|
|
444
|
+
// TypeScript now correctly infers the types thanks to the ArrayElement type in UiSchema
|
|
445
|
+
const result = uiSchema.items(item, index, formContext);
|
|
446
|
+
// Only use the result if it's truthy
|
|
447
|
+
return result as UiSchema<T[], S, F>;
|
|
448
|
+
} catch (e) {
|
|
449
|
+
console.error(`Error executing dynamic uiSchema.items function for item at index ${index}:`, e);
|
|
450
|
+
// Fall back to undefined to allow the field to still render
|
|
451
|
+
return undefined;
|
|
452
|
+
}
|
|
453
|
+
} else {
|
|
454
|
+
// Static object case - preserve undefined to maintain backward compatibility
|
|
455
|
+
return uiSchema.items as UiSchema<T[], S, F> | undefined;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
426
459
|
/** Renders the `ArrayField` depending on the specific needs of the schema and uischema elements
|
|
427
460
|
*/
|
|
428
461
|
render() {
|
|
@@ -500,6 +533,10 @@ class ArrayField<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends For
|
|
|
500
533
|
const itemErrorSchema = errorSchema ? (errorSchema[index] as ErrorSchema<T[]>) : undefined;
|
|
501
534
|
const itemIdPrefix = idSchema.$id + idSeparator + index;
|
|
502
535
|
const itemIdSchema = schemaUtils.toIdSchema(itemSchema, itemIdPrefix, itemCast, idPrefix, idSeparator);
|
|
536
|
+
|
|
537
|
+
// Compute the item UI schema using the helper method
|
|
538
|
+
const itemUiSchema = this.computeItemUiSchema(uiSchema, item, index, formContext);
|
|
539
|
+
|
|
503
540
|
return this.renderArrayFieldItem({
|
|
504
541
|
key,
|
|
505
542
|
index,
|
|
@@ -512,7 +549,7 @@ class ArrayField<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends For
|
|
|
512
549
|
itemIdSchema,
|
|
513
550
|
itemErrorSchema,
|
|
514
551
|
itemData: itemCast,
|
|
515
|
-
itemUiSchema
|
|
552
|
+
itemUiSchema,
|
|
516
553
|
autofocus: autofocus && index === 0,
|
|
517
554
|
onBlur,
|
|
518
555
|
onFocus,
|
|
@@ -751,11 +788,20 @@ class ArrayField<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends For
|
|
|
751
788
|
: itemSchemas[index]) || {};
|
|
752
789
|
const itemIdPrefix = idSchema.$id + idSeparator + index;
|
|
753
790
|
const itemIdSchema = schemaUtils.toIdSchema(itemSchema, itemIdPrefix, itemCast, idPrefix, idSeparator);
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
791
|
+
// Compute the item UI schema - handle both static and dynamic cases
|
|
792
|
+
let itemUiSchema: UiSchema<T[], S, F> | undefined;
|
|
793
|
+
if (additional) {
|
|
794
|
+
// For additional items, use additionalItems uiSchema
|
|
795
|
+
itemUiSchema = uiSchema.additionalItems as UiSchema<T[], S, F>;
|
|
796
|
+
} else {
|
|
797
|
+
// For fixed items, uiSchema.items can be an array, a function, or a single object
|
|
798
|
+
if (Array.isArray(uiSchema.items)) {
|
|
799
|
+
itemUiSchema = uiSchema.items[index] as UiSchema<T[], S, F>;
|
|
800
|
+
} else {
|
|
801
|
+
// Use the helper method for function or static object cases
|
|
802
|
+
itemUiSchema = this.computeItemUiSchema(uiSchema, item, index, formContext);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
759
805
|
const itemErrorSchema = errorSchema ? (errorSchema[index] as ErrorSchema<T[]>) : undefined;
|
|
760
806
|
|
|
761
807
|
return this.renderArrayFieldItem({
|
|
@@ -811,7 +857,7 @@ class ArrayField<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends For
|
|
|
811
857
|
canMoveDown: boolean;
|
|
812
858
|
itemSchema: S;
|
|
813
859
|
itemData: T[];
|
|
814
|
-
itemUiSchema: UiSchema<T[], S, F
|
|
860
|
+
itemUiSchema: UiSchema<T[], S, F> | undefined;
|
|
815
861
|
itemIdSchema: IdSchema<T[]>;
|
|
816
862
|
itemErrorSchema?: ErrorSchema<T[]>;
|
|
817
863
|
autofocus?: boolean;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
1
2
|
import {
|
|
2
3
|
getWidget,
|
|
3
4
|
getUiOptions,
|
|
@@ -5,6 +6,7 @@ import {
|
|
|
5
6
|
FieldProps,
|
|
6
7
|
FormContextType,
|
|
7
8
|
EnumOptionsType,
|
|
9
|
+
ErrorSchema,
|
|
8
10
|
RJSFSchema,
|
|
9
11
|
StrictRJSFSchema,
|
|
10
12
|
TranslatableString,
|
|
@@ -86,6 +88,13 @@ function BooleanField<T = any, S extends StrictRJSFSchema = RJSFSchema, F extend
|
|
|
86
88
|
enumOptions = optionsList<T, S, F>({ enum: enums } as S, uiSchema);
|
|
87
89
|
}
|
|
88
90
|
}
|
|
91
|
+
const onWidgetChange = useCallback(
|
|
92
|
+
(value: T | undefined, errorSchema?: ErrorSchema, id?: string) => {
|
|
93
|
+
// Boolean field change passes an empty path array to the parent field which adds the appropriate path
|
|
94
|
+
return onChange(value, [], errorSchema, id);
|
|
95
|
+
},
|
|
96
|
+
[onChange],
|
|
97
|
+
);
|
|
89
98
|
|
|
90
99
|
return (
|
|
91
100
|
<Widget
|
|
@@ -94,7 +103,7 @@ function BooleanField<T = any, S extends StrictRJSFSchema = RJSFSchema, F extend
|
|
|
94
103
|
uiSchema={uiSchema}
|
|
95
104
|
id={idSchema.$id}
|
|
96
105
|
name={name}
|
|
97
|
-
onChange={
|
|
106
|
+
onChange={onWidgetChange}
|
|
98
107
|
onFocus={onFocus}
|
|
99
108
|
onBlur={onBlur}
|
|
100
109
|
label={label}
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
UI_OPTIONS_KEY,
|
|
25
25
|
UI_GLOBAL_OPTIONS_KEY,
|
|
26
26
|
UiSchema,
|
|
27
|
+
ITEMS_KEY,
|
|
27
28
|
} from '@rjsf/utils';
|
|
28
29
|
import cloneDeep from 'lodash/cloneDeep';
|
|
29
30
|
import each from 'lodash/each';
|
|
@@ -39,6 +40,7 @@ import isObject from 'lodash/isObject';
|
|
|
39
40
|
import isPlainObject from 'lodash/isPlainObject';
|
|
40
41
|
import isString from 'lodash/isString';
|
|
41
42
|
import isUndefined from 'lodash/isUndefined';
|
|
43
|
+
import last from 'lodash/last';
|
|
42
44
|
import set from 'lodash/set';
|
|
43
45
|
|
|
44
46
|
/** The enumeration of the three different Layout GridTemplate type values
|
|
@@ -130,6 +132,15 @@ function getNonNullishValue<T = unknown>(value?: T, fallback?: T): T | undefined
|
|
|
130
132
|
return value ?? fallback;
|
|
131
133
|
}
|
|
132
134
|
|
|
135
|
+
/** Detects if a `str` is made up entirely of numeric characters
|
|
136
|
+
*
|
|
137
|
+
* @param str - The string to check to see if it is a numeric index
|
|
138
|
+
* @return - True if the string consists entirely of numeric characters
|
|
139
|
+
*/
|
|
140
|
+
function isNumericIndex(str: string) {
|
|
141
|
+
return /^\d+?$/.test(str); // Matches positive integers
|
|
142
|
+
}
|
|
143
|
+
|
|
133
144
|
/** The `LayoutGridField` will render a schema, uiSchema and formData combination out into a GridTemplate in the shape
|
|
134
145
|
* described in the uiSchema. To define the grid to use to render the elements within a field in the schema, provide in
|
|
135
146
|
* the uiSchema for that field the object contained under a `ui:layoutGrid` element. E.g. (as a JSON object):
|
|
@@ -496,6 +507,47 @@ export default class LayoutGridField<
|
|
|
496
507
|
return schemaUtils.toIdSchema(schema, baseId, formData, baseId, idSeparator);
|
|
497
508
|
}
|
|
498
509
|
|
|
510
|
+
/** Computes the `rawSchema` and `idSchema` for a `schema` and a `potentialIndex`. If the `schema` is of type array,
|
|
511
|
+
* has an `ITEMS_KEY` element and `potentialIndex` represents a numeric value, the element at `ITEMS_KEY` is checked
|
|
512
|
+
* to see if it is an array. If it is AND the `potentialIndex`th element is available, it is used as the `rawSchema`,
|
|
513
|
+
* otherwise the last value of the element is used. If it is not, then the element is used as the `rawSchema`. In
|
|
514
|
+
* either case, an `idSchema` is computed for the array index. If the `schema` does not represent an array or the
|
|
515
|
+
* `potentialIndex` is not a numeric value, then `rawSchema` is returned as undefined and given `idSchema` is returned
|
|
516
|
+
* as is.
|
|
517
|
+
*
|
|
518
|
+
* @param schema - The schema to generate the idSchema for
|
|
519
|
+
* @param idSchema - The IdSchema for the schema
|
|
520
|
+
* @param potentialIndex - A string containing a potential index
|
|
521
|
+
* @param [idSeparator] - The param to pass into the `toIdSchema` util which will use it to join the `idSchema` paths
|
|
522
|
+
* @returns - An object containing the `rawSchema` and `idSchema` of an array item, otherwise an undefined `rawSchema`
|
|
523
|
+
*/
|
|
524
|
+
static computeArraySchemasIfPresent<T = any, S extends StrictRJSFSchema = RJSFSchema>(
|
|
525
|
+
schema: S | undefined,
|
|
526
|
+
idSchema: IdSchema<T>,
|
|
527
|
+
potentialIndex: string,
|
|
528
|
+
idSeparator?: string,
|
|
529
|
+
): {
|
|
530
|
+
rawSchema?: S;
|
|
531
|
+
idSchema: IdSchema<T>;
|
|
532
|
+
} {
|
|
533
|
+
let rawSchema: S | undefined;
|
|
534
|
+
if (isNumericIndex(potentialIndex) && schema && schema?.type === 'array' && has(schema, ITEMS_KEY)) {
|
|
535
|
+
const index = Number(potentialIndex);
|
|
536
|
+
const items = schema[ITEMS_KEY];
|
|
537
|
+
if (Array.isArray(items)) {
|
|
538
|
+
if (index > items.length) {
|
|
539
|
+
rawSchema = last(items) as S;
|
|
540
|
+
} else {
|
|
541
|
+
rawSchema = items[index] as S;
|
|
542
|
+
}
|
|
543
|
+
} else {
|
|
544
|
+
rawSchema = items as S;
|
|
545
|
+
}
|
|
546
|
+
idSchema = { [ID_KEY]: `${idSchema[ID_KEY]}${idSeparator ?? '_'}${index}` } as IdSchema<T>;
|
|
547
|
+
}
|
|
548
|
+
return { rawSchema, idSchema };
|
|
549
|
+
}
|
|
550
|
+
|
|
499
551
|
/** Given a `dottedPath` to a field in the `initialSchema`, iterate through each individual path in the schema until
|
|
500
552
|
* the leaf path is found and returned (along with whether that leaf path `isRequired`) OR no schema exists for an
|
|
501
553
|
* element in the path. If the leaf schema element happens to be a oneOf/anyOf then also return the oneOf/anyOf as
|
|
@@ -552,7 +604,9 @@ export default class LayoutGridField<
|
|
|
552
604
|
rawSchema = get(selectedSchema, [PROPERTIES_KEY, part], {}) as S;
|
|
553
605
|
idSchema = get(selectedIdSchema, part, {}) as IdSchema<T>;
|
|
554
606
|
} else {
|
|
555
|
-
|
|
607
|
+
const result = LayoutGridField.computeArraySchemasIfPresent<T, S>(schema, idSchema, part, idSeparator);
|
|
608
|
+
rawSchema = result.rawSchema ?? ({} as S);
|
|
609
|
+
idSchema = result.idSchema;
|
|
556
610
|
}
|
|
557
611
|
// Now drill into the innerData for the part, returning an empty object by default if it doesn't exist
|
|
558
612
|
innerData = get(innerData, part, {}) as T;
|
|
@@ -578,11 +632,17 @@ export default class LayoutGridField<
|
|
|
578
632
|
idSchema = mergeObjects(rawIdSchema, idSchema) as IdSchema<T>;
|
|
579
633
|
}
|
|
580
634
|
isRequired = schema !== undefined && Array.isArray(schema.required) && includes(schema.required, leafPath);
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
635
|
+
const result = LayoutGridField.computeArraySchemasIfPresent<T, S>(schema, idSchema, leafPath, idSeparator);
|
|
636
|
+
if (result.rawSchema) {
|
|
637
|
+
schema = result.rawSchema;
|
|
638
|
+
idSchema = result.idSchema;
|
|
639
|
+
} else {
|
|
640
|
+
// Now grab the schema from the leafPath of the current schema properties
|
|
641
|
+
schema = get(schema, [PROPERTIES_KEY, leafPath]) as S | undefined;
|
|
642
|
+
// Resolve any `$ref`s for the current schema
|
|
643
|
+
schema = schema ? schemaUtils.retrieveSchema(schema) : schema;
|
|
644
|
+
idSchema = get(idSchema, leafPath, {}) as IdSchema<T>;
|
|
645
|
+
}
|
|
586
646
|
isReadonly = getNonNullishValue(schema?.readOnly, isReadonly);
|
|
587
647
|
if (schema && (has(schema, ONE_OF_KEY) || has(schema, ANY_OF_KEY))) {
|
|
588
648
|
const xxx = has(schema, ONE_OF_KEY) ? ONE_OF_KEY : ANY_OF_KEY;
|
|
@@ -676,16 +736,14 @@ export default class LayoutGridField<
|
|
|
676
736
|
* @returns - The `onChange` handling function for the `dottedPath` field
|
|
677
737
|
*/
|
|
678
738
|
onFieldChange = (dottedPath: string) => {
|
|
679
|
-
return (value:
|
|
680
|
-
const { onChange, errorSchema
|
|
681
|
-
const newFormData = cloneDeep(formData || ({} as T));
|
|
739
|
+
return (value: T | undefined, path?: (number | string)[], errSchema?: ErrorSchema<T>, id?: string) => {
|
|
740
|
+
const { onChange, errorSchema } = this.props;
|
|
682
741
|
let newErrorSchema = errorSchema;
|
|
683
742
|
if (errSchema && errorSchema) {
|
|
684
743
|
newErrorSchema = cloneDeep(errorSchema);
|
|
685
744
|
set(newErrorSchema, dottedPath, errSchema);
|
|
686
745
|
}
|
|
687
|
-
|
|
688
|
-
onChange(newFormData, newErrorSchema, id);
|
|
746
|
+
onChange(value, dottedPath.split('.'), newErrorSchema, id);
|
|
689
747
|
};
|
|
690
748
|
};
|
|
691
749
|
|
|
@@ -171,7 +171,8 @@ export default function LayoutMultiSchemaField<
|
|
|
171
171
|
if (newFormData) {
|
|
172
172
|
set(newFormData, selectorField, opt);
|
|
173
173
|
}
|
|
174
|
-
|
|
174
|
+
// Pass the component name in the path
|
|
175
|
+
onChange(newFormData, [name], undefined, id);
|
|
175
176
|
};
|
|
176
177
|
|
|
177
178
|
// filtering the options based on the type of widget because `selectField` does not recognize the `convertOther` prop
|
|
@@ -131,7 +131,8 @@ class AnyOfField<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends For
|
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
this.setState({ selectedOption: intOption }, () => {
|
|
134
|
-
|
|
134
|
+
// Changing the option will pass an empty path array to the parent field which will add the appropriate path
|
|
135
|
+
onChange(newFormData, [], undefined, this.getFieldId());
|
|
135
136
|
});
|
|
136
137
|
};
|
|
137
138
|
|
|
@@ -9,12 +9,12 @@ import { FieldProps, FormContextType, RJSFSchema, StrictRJSFSchema } from '@rjsf
|
|
|
9
9
|
function NullField<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any>(
|
|
10
10
|
props: FieldProps<T, S, F>,
|
|
11
11
|
) {
|
|
12
|
-
const { formData, onChange } = props;
|
|
12
|
+
const { name, formData, onChange } = props;
|
|
13
13
|
useEffect(() => {
|
|
14
14
|
if (formData === undefined) {
|
|
15
|
-
onChange(null as unknown as T);
|
|
15
|
+
onChange(null as unknown as T, [name]);
|
|
16
16
|
}
|
|
17
|
-
}, [formData, onChange]);
|
|
17
|
+
}, [name, formData, onChange]);
|
|
18
18
|
|
|
19
19
|
return null;
|
|
20
20
|
}
|
|
@@ -44,7 +44,7 @@ function NumberField<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends
|
|
|
44
44
|
* @param value - The current value for the change occurring
|
|
45
45
|
*/
|
|
46
46
|
const handleChange = useCallback(
|
|
47
|
-
(value: FieldProps<T, S, F>['value'], errorSchema?: ErrorSchema<T>, id?: string) => {
|
|
47
|
+
(value: FieldProps<T, S, F>['value'], path?: (number | string)[], errorSchema?: ErrorSchema<T>, id?: string) => {
|
|
48
48
|
// Cache the original value in component state
|
|
49
49
|
setLastValue(value);
|
|
50
50
|
|
|
@@ -62,7 +62,7 @@ function NumberField<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends
|
|
|
62
62
|
? asNumber(value.replace(trailingCharMatcher, ''))
|
|
63
63
|
: asNumber(value);
|
|
64
64
|
|
|
65
|
-
onChange(processed as unknown as T, errorSchema, id);
|
|
65
|
+
onChange(processed as unknown as T, path, errorSchema, id);
|
|
66
66
|
},
|
|
67
67
|
[onChange],
|
|
68
68
|
);
|