@rjsf/core 6.6.2 → 6.7.0
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/LICENSE.md +201 -0
- package/dist/core.umd.js +274 -245
- package/dist/index.cjs +911 -873
- package/dist/index.cjs.map +4 -4
- package/dist/index.esm.js +702 -659
- package/dist/index.esm.js.map +4 -4
- package/lib/components/Form.d.ts +2 -2
- package/lib/components/Form.d.ts.map +1 -1
- package/lib/components/Form.js +30 -15
- package/lib/components/RichDescription.d.ts +1 -1
- package/lib/components/RichDescription.d.ts.map +1 -1
- package/lib/components/RichHelp.d.ts +1 -1
- package/lib/components/RichHelp.d.ts.map +1 -1
- package/lib/components/SchemaExamples.d.ts +1 -1
- package/lib/components/SchemaExamples.d.ts.map +1 -1
- package/lib/components/fields/ArrayField.d.ts +1 -1
- package/lib/components/fields/ArrayField.d.ts.map +1 -1
- package/lib/components/fields/BooleanField.d.ts +1 -1
- package/lib/components/fields/BooleanField.d.ts.map +1 -1
- package/lib/components/fields/CyclicSchemaField.d.ts +8 -0
- package/lib/components/fields/CyclicSchemaField.d.ts.map +1 -0
- package/lib/components/fields/CyclicSchemaField.js +21 -0
- package/lib/components/fields/FallbackField.d.ts +1 -1
- package/lib/components/fields/FallbackField.d.ts.map +1 -1
- package/lib/components/fields/LayoutGridField.d.ts +1 -1
- package/lib/components/fields/LayoutGridField.d.ts.map +1 -1
- package/lib/components/fields/LayoutHeaderField.d.ts +1 -1
- package/lib/components/fields/LayoutHeaderField.d.ts.map +1 -1
- package/lib/components/fields/LayoutMultiSchemaField.d.ts +1 -1
- package/lib/components/fields/LayoutMultiSchemaField.d.ts.map +1 -1
- package/lib/components/fields/MultiSchemaField.d.ts +1 -46
- package/lib/components/fields/MultiSchemaField.d.ts.map +1 -1
- package/lib/components/fields/MultiSchemaField.js +111 -135
- package/lib/components/fields/NumberField.d.ts +1 -1
- package/lib/components/fields/NumberField.d.ts.map +1 -1
- package/lib/components/fields/NumberField.js +1 -1
- package/lib/components/fields/ObjectField.d.ts +1 -1
- package/lib/components/fields/ObjectField.d.ts.map +1 -1
- package/lib/components/fields/ObjectField.js +2 -2
- package/lib/components/fields/OptionalDataControlsField.d.ts +1 -1
- package/lib/components/fields/OptionalDataControlsField.d.ts.map +1 -1
- package/lib/components/fields/SchemaField.d.ts +13 -7
- package/lib/components/fields/SchemaField.d.ts.map +1 -1
- package/lib/components/fields/SchemaField.js +37 -24
- package/lib/components/fields/StringField.d.ts +1 -1
- package/lib/components/fields/StringField.d.ts.map +1 -1
- package/lib/components/fields/index.d.ts.map +1 -1
- package/lib/components/fields/index.js +2 -0
- package/lib/components/templates/ArrayFieldDescriptionTemplate.d.ts +1 -1
- package/lib/components/templates/ArrayFieldDescriptionTemplate.d.ts.map +1 -1
- package/lib/components/templates/ArrayFieldItemButtonsTemplate.d.ts +1 -1
- package/lib/components/templates/ArrayFieldItemButtonsTemplate.d.ts.map +1 -1
- package/lib/components/templates/ArrayFieldItemTemplate.d.ts +1 -1
- package/lib/components/templates/ArrayFieldItemTemplate.d.ts.map +1 -1
- package/lib/components/templates/ArrayFieldTemplate.d.ts +1 -1
- package/lib/components/templates/ArrayFieldTemplate.d.ts.map +1 -1
- package/lib/components/templates/ArrayFieldTitleTemplate.d.ts +1 -1
- package/lib/components/templates/ArrayFieldTitleTemplate.d.ts.map +1 -1
- package/lib/components/templates/BaseInputTemplate.d.ts +1 -1
- package/lib/components/templates/BaseInputTemplate.d.ts.map +1 -1
- package/lib/components/templates/BaseInputTemplate.js +2 -2
- package/lib/components/templates/ButtonTemplates/AddButton.d.ts +1 -1
- package/lib/components/templates/ButtonTemplates/AddButton.d.ts.map +1 -1
- package/lib/components/templates/ButtonTemplates/IconButton.d.ts +6 -6
- package/lib/components/templates/ButtonTemplates/IconButton.d.ts.map +1 -1
- package/lib/components/templates/ButtonTemplates/SubmitButton.d.ts +1 -1
- package/lib/components/templates/ButtonTemplates/SubmitButton.d.ts.map +1 -1
- package/lib/components/templates/CyclicSchemaExpandTemplate.d.ts +7 -0
- package/lib/components/templates/CyclicSchemaExpandTemplate.d.ts.map +1 -0
- package/lib/components/templates/CyclicSchemaExpandTemplate.js +12 -0
- package/lib/components/templates/DescriptionField.d.ts +1 -1
- package/lib/components/templates/DescriptionField.d.ts.map +1 -1
- package/lib/components/templates/ErrorList.d.ts +1 -1
- package/lib/components/templates/ErrorList.d.ts.map +1 -1
- package/lib/components/templates/FallbackFieldTemplate.d.ts +1 -1
- package/lib/components/templates/FallbackFieldTemplate.d.ts.map +1 -1
- package/lib/components/templates/FieldErrorTemplate.d.ts +1 -1
- package/lib/components/templates/FieldErrorTemplate.d.ts.map +1 -1
- package/lib/components/templates/FieldHelpTemplate.d.ts +1 -1
- package/lib/components/templates/FieldHelpTemplate.d.ts.map +1 -1
- package/lib/components/templates/FieldTemplate/FieldTemplate.d.ts +1 -1
- package/lib/components/templates/FieldTemplate/FieldTemplate.d.ts.map +1 -1
- package/lib/components/templates/FieldTemplate/Label.d.ts +1 -1
- package/lib/components/templates/FieldTemplate/Label.d.ts.map +1 -1
- package/lib/components/templates/GridTemplate.d.ts +1 -1
- package/lib/components/templates/GridTemplate.d.ts.map +1 -1
- package/lib/components/templates/MultiSchemaFieldTemplate.d.ts +1 -1
- package/lib/components/templates/MultiSchemaFieldTemplate.d.ts.map +1 -1
- package/lib/components/templates/ObjectFieldTemplate.d.ts +1 -1
- package/lib/components/templates/ObjectFieldTemplate.d.ts.map +1 -1
- package/lib/components/templates/OptionalDataControlsTemplate.d.ts +1 -1
- package/lib/components/templates/OptionalDataControlsTemplate.d.ts.map +1 -1
- package/lib/components/templates/TitleField.d.ts +1 -1
- package/lib/components/templates/TitleField.d.ts.map +1 -1
- package/lib/components/templates/UnsupportedField.d.ts +1 -1
- package/lib/components/templates/UnsupportedField.d.ts.map +1 -1
- package/lib/components/templates/UnsupportedField.js +1 -1
- package/lib/components/templates/WrapIfAdditionalTemplate.d.ts +1 -1
- package/lib/components/templates/WrapIfAdditionalTemplate.d.ts.map +1 -1
- package/lib/components/templates/index.d.ts.map +1 -1
- package/lib/components/templates/index.js +2 -0
- package/lib/components/widgets/AltDateTimeWidget.d.ts +1 -1
- package/lib/components/widgets/AltDateTimeWidget.d.ts.map +1 -1
- package/lib/components/widgets/AltDateWidget.d.ts +1 -1
- package/lib/components/widgets/AltDateWidget.d.ts.map +1 -1
- package/lib/components/widgets/CheckboxWidget.d.ts +1 -1
- package/lib/components/widgets/CheckboxWidget.d.ts.map +1 -1
- package/lib/components/widgets/CheckboxesWidget.d.ts +1 -1
- package/lib/components/widgets/CheckboxesWidget.d.ts.map +1 -1
- package/lib/components/widgets/CheckboxesWidget.js +2 -2
- package/lib/components/widgets/ColorWidget.d.ts +1 -1
- package/lib/components/widgets/ColorWidget.d.ts.map +1 -1
- package/lib/components/widgets/DateTimeWidget.d.ts +1 -1
- package/lib/components/widgets/DateTimeWidget.d.ts.map +1 -1
- package/lib/components/widgets/DateWidget.d.ts +1 -1
- package/lib/components/widgets/DateWidget.d.ts.map +1 -1
- package/lib/components/widgets/EmailWidget.d.ts +1 -1
- package/lib/components/widgets/EmailWidget.d.ts.map +1 -1
- package/lib/components/widgets/FileWidget.d.ts +1 -1
- package/lib/components/widgets/FileWidget.d.ts.map +1 -1
- package/lib/components/widgets/HiddenWidget.d.ts +1 -1
- package/lib/components/widgets/HiddenWidget.d.ts.map +1 -1
- package/lib/components/widgets/PasswordWidget.d.ts +1 -1
- package/lib/components/widgets/PasswordWidget.d.ts.map +1 -1
- package/lib/components/widgets/RadioWidget.d.ts +1 -1
- package/lib/components/widgets/RadioWidget.d.ts.map +1 -1
- package/lib/components/widgets/RadioWidget.js +2 -2
- package/lib/components/widgets/RangeWidget.d.ts +1 -1
- package/lib/components/widgets/RangeWidget.d.ts.map +1 -1
- package/lib/components/widgets/RatingWidget.d.ts +1 -1
- package/lib/components/widgets/RatingWidget.d.ts.map +1 -1
- package/lib/components/widgets/SelectWidget.d.ts +1 -1
- package/lib/components/widgets/SelectWidget.d.ts.map +1 -1
- package/lib/components/widgets/SelectWidget.js +9 -8
- package/lib/components/widgets/TextWidget.d.ts +1 -1
- package/lib/components/widgets/TextWidget.d.ts.map +1 -1
- package/lib/components/widgets/TextareaWidget.d.ts +1 -1
- package/lib/components/widgets/TextareaWidget.d.ts.map +1 -1
- package/lib/components/widgets/TextareaWidget.js +3 -3
- package/lib/components/widgets/TimeWidget.d.ts +1 -1
- package/lib/components/widgets/TimeWidget.d.ts.map +1 -1
- package/lib/components/widgets/URLWidget.d.ts +1 -1
- package/lib/components/widgets/URLWidget.d.ts.map +1 -1
- package/lib/components/widgets/UpDownWidget.d.ts +1 -1
- package/lib/components/widgets/UpDownWidget.d.ts.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +28 -27
- package/src/components/Form.tsx +34 -15
- package/src/components/fields/CyclicSchemaField.tsx +40 -0
- package/src/components/fields/MultiSchemaField.tsx +201 -230
- package/src/components/fields/NumberField.tsx +1 -1
- package/src/components/fields/ObjectField.tsx +5 -2
- package/src/components/fields/SchemaField.tsx +46 -34
- package/src/components/fields/index.ts +2 -0
- package/src/components/templates/BaseInputTemplate.tsx +2 -5
- package/src/components/templates/CyclicSchemaExpandTemplate.tsx +31 -0
- package/src/components/templates/UnsupportedField.tsx +1 -1
- package/src/components/templates/index.ts +2 -0
- package/src/components/widgets/CheckboxesWidget.tsx +2 -2
- package/src/components/widgets/RadioWidget.tsx +2 -2
- package/src/components/widgets/SelectWidget.tsx +45 -30
- package/src/components/widgets/TextareaWidget.tsx +3 -3
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import type { FieldProps, FormContextType, RJSFSchema, StrictRJSFSchema } from '@rjsf/utils';
|
|
3
|
+
import { getTemplate, getUiOptions, RJSF_REF_CYCLE_KEY } from '@rjsf/utils';
|
|
4
|
+
|
|
5
|
+
/** The `CyclicSchemaField` component is used to render a field in the schema that is marked with RJSF_REF_CYCLE_KEY ===
|
|
6
|
+
* true
|
|
7
|
+
*
|
|
8
|
+
* @param props - The `FieldProps` for this template
|
|
9
|
+
*/
|
|
10
|
+
export default function CyclicSchemaField<
|
|
11
|
+
T = any,
|
|
12
|
+
S extends StrictRJSFSchema = RJSFSchema,
|
|
13
|
+
F extends FormContextType = any,
|
|
14
|
+
>(props: FieldProps<T, S, F>) {
|
|
15
|
+
const [expanded, setExpanded] = useState(false);
|
|
16
|
+
const { name, registry, schema, uiSchema, fieldPathId } = props;
|
|
17
|
+
const { globalUiOptions } = registry;
|
|
18
|
+
|
|
19
|
+
const uiOptions = getUiOptions<T, S, F>(uiSchema, globalUiOptions);
|
|
20
|
+
const CyclicSchemaExpandTemplate = getTemplate<'CyclicSchemaExpandTemplate', T, S, F>(
|
|
21
|
+
'CyclicSchemaExpandTemplate',
|
|
22
|
+
registry,
|
|
23
|
+
uiOptions,
|
|
24
|
+
);
|
|
25
|
+
if (!expanded) {
|
|
26
|
+
return (
|
|
27
|
+
<CyclicSchemaExpandTemplate
|
|
28
|
+
registry={registry}
|
|
29
|
+
schema={schema}
|
|
30
|
+
uiSchema={uiSchema}
|
|
31
|
+
name={name}
|
|
32
|
+
fieldPathId={fieldPathId}
|
|
33
|
+
onExpand={() => setExpanded(true)}
|
|
34
|
+
/>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
const { fields } = registry;
|
|
38
|
+
const { SchemaField } = fields;
|
|
39
|
+
return <SchemaField {...props} schema={{ ...schema, [RJSF_REF_CYCLE_KEY]: false }} />;
|
|
40
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
2
|
import type { FieldProps, FormContextType, RJSFSchema, StrictRJSFSchema, UiSchema } from '@rjsf/utils';
|
|
3
3
|
import {
|
|
4
4
|
ANY_OF_KEY,
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
getTemplate,
|
|
9
9
|
getUiOptions,
|
|
10
10
|
getWidget,
|
|
11
|
+
hashObject,
|
|
11
12
|
isFormDataAvailable,
|
|
12
13
|
mergeSchemas,
|
|
13
14
|
ONE_OF_KEY,
|
|
@@ -18,102 +19,86 @@ import get from 'lodash/get';
|
|
|
18
19
|
import isEmpty from 'lodash/isEmpty';
|
|
19
20
|
import omit from 'lodash/omit';
|
|
20
21
|
|
|
21
|
-
/** Type used for the state of the `AnyOfField` component */
|
|
22
|
-
interface AnyOfFieldState<S extends StrictRJSFSchema = RJSFSchema> {
|
|
23
|
-
/** The currently selected option */
|
|
24
|
-
selectedOption: number;
|
|
25
|
-
/** The option schemas after retrieving all $refs */
|
|
26
|
-
retrievedOptions: S[];
|
|
27
|
-
}
|
|
28
|
-
|
|
29
22
|
/** The `AnyOfField` component is used to render a field in the schema that is an `anyOf`, `allOf` or `oneOf`. It tracks
|
|
30
23
|
* the currently selected option and cleans up any irrelevant data in `formData`.
|
|
31
24
|
*
|
|
32
25
|
* @param props - The `FieldProps` for this template
|
|
33
26
|
*/
|
|
34
|
-
|
|
35
|
-
FieldProps<T, S, F>,
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
27
|
+
function AnyOfField<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any>(
|
|
28
|
+
props: FieldProps<T, S, F>,
|
|
29
|
+
) {
|
|
30
|
+
const {
|
|
31
|
+
name,
|
|
32
|
+
disabled = false,
|
|
33
|
+
errorSchema = {},
|
|
34
|
+
formData,
|
|
35
|
+
fieldPathId,
|
|
36
|
+
onBlur,
|
|
37
|
+
onChange,
|
|
38
|
+
onFocus,
|
|
39
|
+
options,
|
|
40
|
+
readonly,
|
|
41
|
+
registry,
|
|
42
|
+
required = false,
|
|
43
|
+
schema,
|
|
44
|
+
uiSchema,
|
|
45
|
+
} = props;
|
|
46
|
+
const { schemaUtils } = registry;
|
|
50
47
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
registry: { schemaUtils },
|
|
55
|
-
} = this.props;
|
|
56
|
-
// cache the retrieved options in state in case they have $refs to save doing it later
|
|
57
|
-
const retrievedOptions = options.map((opt: S) => schemaUtils.retrieveSchema(opt, formData));
|
|
48
|
+
// Hash formData by value so the memo only invalidates when data actually changes, not on every
|
|
49
|
+
// new object reference. hashObject(undefined) throws, so null is used as the fallback.
|
|
50
|
+
const formDataHash = hashObject(formData ?? null);
|
|
58
51
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
52
|
+
// retrievedOptions is purely derived from options — useMemo handles re-derivation automatically
|
|
53
|
+
// when options, schemaUtils, or formData's value changes, with no render-phase dispatch needed.
|
|
54
|
+
const retrievedOptions = useMemo(
|
|
55
|
+
() => options.map((opt: S) => schemaUtils.retrieveSchema(opt, formData)),
|
|
56
|
+
// oxlint-disable-next-line react-hooks/exhaustive-deps -- formDataHash is the value-stable proxy for formData
|
|
57
|
+
[options, schemaUtils, formDataHash],
|
|
58
|
+
);
|
|
64
59
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
60
|
+
const [selectedOption, setSelectedOption] = useState<number>(() => {
|
|
61
|
+
const discriminator = getDiscriminatorFieldFromSchema<S>(schema);
|
|
62
|
+
return schemaUtils.getClosestMatchingOption(formData, retrievedOptions, 0, discriminator);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
/** Flag to skip the formData-change-driven option recalculation when the user just selected an option.
|
|
66
|
+
* Set to true in onOptionChange (before onChange is called), consumed and reset in the update effect.
|
|
67
|
+
* This prevents the matching-option recalculation from overriding a user's explicit choice when
|
|
68
|
+
* getDefaultFormState populates undefined properties that make deepEquals see a false formData change.
|
|
70
69
|
*/
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
if (!deepEquals(formData, prevProps.formData) && fieldPathId.$id === prevProps.fieldPathId.$id) {
|
|
84
|
-
if (this.skipNextOptionRecalculation) {
|
|
85
|
-
this.skipNextOptionRecalculation = false;
|
|
86
|
-
} else {
|
|
87
|
-
const { retrievedOptions } = newState;
|
|
88
|
-
const matchingOption = this.getMatchingOption(selectedOption, formData, retrievedOptions);
|
|
70
|
+
const skipNextOptionRecalculation = useRef(false);
|
|
71
|
+
const prevFormDataRef = useRef<T | undefined>(formData);
|
|
72
|
+
const prevFieldIdRef = useRef(fieldPathId.$id);
|
|
73
|
+
|
|
74
|
+
// Mirrors componentDidUpdate: re-match selectedOption when formData changes on the same field.
|
|
75
|
+
// Runs after every render (no deps array) to compare against prev values stored in refs.
|
|
76
|
+
// oxlint-disable-next-line react-hooks/exhaustive-deps
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
const prevFormData = prevFormDataRef.current;
|
|
79
|
+
const prevFieldId = prevFieldIdRef.current;
|
|
80
|
+
prevFormDataRef.current = formData;
|
|
81
|
+
prevFieldIdRef.current = fieldPathId.$id;
|
|
89
82
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
83
|
+
if (!deepEquals(formData, prevFormData) && fieldPathId.$id === prevFieldId) {
|
|
84
|
+
if (skipNextOptionRecalculation.current) {
|
|
85
|
+
skipNextOptionRecalculation.current = false;
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const discriminator = getDiscriminatorFieldFromSchema<S>(schema);
|
|
89
|
+
const matchingOption = schemaUtils.getClosestMatchingOption(
|
|
90
|
+
formData,
|
|
91
|
+
retrievedOptions,
|
|
92
|
+
selectedOption,
|
|
93
|
+
discriminator,
|
|
94
|
+
);
|
|
95
|
+
if (matchingOption !== selectedOption) {
|
|
96
|
+
setSelectedOption(matchingOption);
|
|
93
97
|
}
|
|
94
98
|
}
|
|
95
|
-
|
|
96
|
-
// oxlint-disable-next-line react/no-did-update-set-state -- guarded to prevent infinite loop
|
|
97
|
-
this.setState(newState);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
99
|
+
});
|
|
100
100
|
|
|
101
|
-
|
|
102
|
-
*
|
|
103
|
-
* @param formData - The new formData
|
|
104
|
-
* @param options - The list of options to choose from
|
|
105
|
-
* @return - The index of the `option` that best matches the `formData`
|
|
106
|
-
*/
|
|
107
|
-
getMatchingOption(selectedOption: number, formData: T | undefined, options: S[]) {
|
|
108
|
-
const {
|
|
109
|
-
schema,
|
|
110
|
-
registry: { schemaUtils },
|
|
111
|
-
} = this.props;
|
|
112
|
-
|
|
113
|
-
const discriminator = getDiscriminatorFieldFromSchema<S>(schema);
|
|
114
|
-
const option = schemaUtils.getClosestMatchingOption(formData, options, selectedOption, discriminator);
|
|
115
|
-
return option;
|
|
116
|
-
}
|
|
101
|
+
const fieldId = `${fieldPathId.$id}${schema.oneOf ? '__oneof_select' : '__anyof_select'}`;
|
|
117
102
|
|
|
118
103
|
/** Callback handler to remember what the currently selected option is. In addition to that the `formData` is updated
|
|
119
104
|
* to remove properties that are not part of the newly selected option schema, and then the updated data is passed to
|
|
@@ -121,166 +106,152 @@ class AnyOfField<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends For
|
|
|
121
106
|
*
|
|
122
107
|
* @param option - The new option value being selected
|
|
123
108
|
*/
|
|
124
|
-
onOptionChange = (
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
const newOption = intOption >= 0 ? retrievedOptions[intOption] : undefined;
|
|
136
|
-
const oldOption = selectedOption >= 0 ? retrievedOptions[selectedOption] : undefined;
|
|
137
|
-
|
|
138
|
-
let newFormData = schemaUtils.sanitizeDataForNewSchema(newOption, oldOption, formData);
|
|
139
|
-
if (newOption) {
|
|
140
|
-
// Call getDefaultFormState to make sure defaults are populated on change. Pass "excludeObjectChildren"
|
|
141
|
-
// so that only the root objects themselves are created without adding undefined children properties
|
|
142
|
-
newFormData = schemaUtils.getDefaultFormState(newOption, newFormData, 'excludeObjectChildren') as T;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
this.setState({ selectedOption: intOption }, () => {
|
|
146
|
-
this.skipNextOptionRecalculation = true;
|
|
147
|
-
onChange(newFormData, fieldPathId.path, undefined, this.getFieldId());
|
|
148
|
-
});
|
|
149
|
-
};
|
|
109
|
+
const onOptionChange = useCallback(
|
|
110
|
+
(option?: string) => {
|
|
111
|
+
if (disabled || readonly) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const intOption = option !== undefined ? parseInt(option, 10) : -1;
|
|
115
|
+
if (intOption === selectedOption) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const newOption = intOption >= 0 ? retrievedOptions[intOption] : undefined;
|
|
119
|
+
const oldOption = selectedOption >= 0 ? retrievedOptions[selectedOption] : undefined;
|
|
150
120
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
121
|
+
let newFormData = schemaUtils.sanitizeDataForNewSchema(newOption, oldOption, formData);
|
|
122
|
+
if (newOption) {
|
|
123
|
+
// Call getDefaultFormState to make sure defaults are populated on change. Pass "excludeObjectChildren"
|
|
124
|
+
// so that only the root objects themselves are created without adding undefined children properties
|
|
125
|
+
newFormData = schemaUtils.getDefaultFormState(newOption, newFormData, 'excludeObjectChildren') as T;
|
|
126
|
+
}
|
|
155
127
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
formData,
|
|
164
|
-
onBlur,
|
|
165
|
-
onFocus,
|
|
166
|
-
readonly,
|
|
167
|
-
required = false,
|
|
168
|
-
registry,
|
|
169
|
-
schema,
|
|
170
|
-
uiSchema,
|
|
171
|
-
} = this.props;
|
|
128
|
+
setSelectedOption(intOption);
|
|
129
|
+
skipNextOptionRecalculation.current = true;
|
|
130
|
+
onChange(newFormData, fieldPathId.path, undefined, fieldId);
|
|
131
|
+
},
|
|
132
|
+
// setSelectedOption is stable (guaranteed by useState); skipNextOptionRecalculation is a ref
|
|
133
|
+
[selectedOption, retrievedOptions, disabled, readonly, schemaUtils, formData, fieldPathId, onChange, fieldId],
|
|
134
|
+
);
|
|
172
135
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
136
|
+
const { widgets, fields, translateString, globalUiOptions } = registry;
|
|
137
|
+
const { SchemaField: SchemaFieldComponent } = fields;
|
|
138
|
+
const MultiSchemaFieldTemplate = getTemplate<'MultiSchemaFieldTemplate', T, S, F>(
|
|
139
|
+
'MultiSchemaFieldTemplate',
|
|
140
|
+
registry,
|
|
141
|
+
globalUiOptions,
|
|
142
|
+
);
|
|
143
|
+
const isOptionalRender = shouldRenderOptionalField<T, S, F>(registry, schema, required, uiSchema);
|
|
144
|
+
const hasFormData = isFormDataAvailable<T>(formData);
|
|
182
145
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
const displayLabel = schemaUtils.getDisplayLabel(schema, uiSchema, globalUiOptions);
|
|
146
|
+
const {
|
|
147
|
+
widget = 'select',
|
|
148
|
+
placeholder,
|
|
149
|
+
autofocus,
|
|
150
|
+
autocomplete,
|
|
151
|
+
title = schema.title,
|
|
152
|
+
...uiOptions
|
|
153
|
+
} = getUiOptions<T, S, F>(uiSchema, globalUiOptions);
|
|
154
|
+
const Widget = getWidget<T, S, F>({ type: 'number' }, widget, widgets);
|
|
155
|
+
const rawErrors = get(errorSchema, ERRORS_KEY, []);
|
|
156
|
+
const fieldErrorSchema = omit(errorSchema, [ERRORS_KEY]);
|
|
157
|
+
const displayLabel = schemaUtils.getDisplayLabel(schema, uiSchema, globalUiOptions);
|
|
196
158
|
|
|
197
|
-
|
|
198
|
-
|
|
159
|
+
const option = selectedOption >= 0 ? retrievedOptions[selectedOption] || null : null;
|
|
160
|
+
let optionSchema: S | undefined | null;
|
|
199
161
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
162
|
+
if (option) {
|
|
163
|
+
const { required: schemaRequired, type: schemaType } = schema;
|
|
164
|
+
const parentProps: Partial<S> = {};
|
|
165
|
+
if (schemaRequired) {
|
|
166
|
+
parentProps.required = schemaRequired as S['required'];
|
|
167
|
+
}
|
|
168
|
+
// Propagate the parent schema type to options that don't define their own.
|
|
169
|
+
// This is necessary when the parent constrains the type (e.g. { type: 'string',
|
|
170
|
+
// oneOf: [{ pattern: '...' }, { pattern: '...' }] }) but the option sub-schemas
|
|
171
|
+
// omit the type — without it, getSchemaType returns undefined and the option
|
|
172
|
+
// renders as FallbackField instead of the correct widget (e.g. StringField).
|
|
173
|
+
if (schemaType !== undefined && !('type' in option)) {
|
|
174
|
+
parentProps.type = schemaType;
|
|
205
175
|
}
|
|
176
|
+
// Merge in all the non-oneOf/anyOf properties and also skip the special ADDITIONAL_PROPERTY_FLAG property
|
|
177
|
+
optionSchema = Object.keys(parentProps).length > 0 ? (mergeSchemas(parentProps, option) as S) : option;
|
|
178
|
+
}
|
|
206
179
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
}
|
|
216
|
-
} else if (ANY_OF_KEY in schema && uiSchema && ANY_OF_KEY in uiSchema) {
|
|
217
|
-
if (Array.isArray(uiSchema[ANY_OF_KEY])) {
|
|
218
|
-
optionsUiSchema = uiSchema[ANY_OF_KEY];
|
|
219
|
-
} else {
|
|
220
|
-
// oxlint-disable-next-line no-console
|
|
221
|
-
console.warn(`uiSchema.anyOf is not an array for "${title || name}"`);
|
|
222
|
-
}
|
|
180
|
+
// First we will check to see if there is an anyOf/oneOf override for the UI schema
|
|
181
|
+
let optionsUiSchema: UiSchema<T, S, F>[] = [];
|
|
182
|
+
if (ONE_OF_KEY in schema && uiSchema && ONE_OF_KEY in uiSchema) {
|
|
183
|
+
if (Array.isArray(uiSchema[ONE_OF_KEY])) {
|
|
184
|
+
optionsUiSchema = uiSchema[ONE_OF_KEY];
|
|
185
|
+
} else {
|
|
186
|
+
// oxlint-disable-next-line no-console
|
|
187
|
+
console.warn(`uiSchema.oneOf is not an array for "${title || name}"`);
|
|
223
188
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
189
|
+
} else if (ANY_OF_KEY in schema && uiSchema && ANY_OF_KEY in uiSchema) {
|
|
190
|
+
if (Array.isArray(uiSchema[ANY_OF_KEY])) {
|
|
191
|
+
optionsUiSchema = uiSchema[ANY_OF_KEY];
|
|
192
|
+
} else {
|
|
193
|
+
// oxlint-disable-next-line no-console
|
|
194
|
+
console.warn(`uiSchema.anyOf is not an array for "${title || name}"`);
|
|
228
195
|
}
|
|
196
|
+
}
|
|
197
|
+
// Then we pick the one that matches the selected option index, if one exists otherwise default to the main uiSchema
|
|
198
|
+
let optionUiSchema = uiSchema;
|
|
199
|
+
if (selectedOption >= 0 && optionsUiSchema.length > selectedOption) {
|
|
200
|
+
optionUiSchema = optionsUiSchema[selectedOption];
|
|
201
|
+
}
|
|
229
202
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
const selector =
|
|
244
|
-
!isOptionalRender || hasFormData ? (
|
|
245
|
-
<Widget
|
|
246
|
-
id={this.getFieldId()}
|
|
247
|
-
name={`${name}${schema.oneOf ? '__oneof_select' : '__anyof_select'}`}
|
|
248
|
-
schema={{ type: 'number', default: 0 } as S}
|
|
249
|
-
onChange={this.onOptionChange}
|
|
250
|
-
onBlur={onBlur}
|
|
251
|
-
onFocus={onFocus}
|
|
252
|
-
disabled={disabled || isEmpty(enumOptions)}
|
|
253
|
-
multiple={false}
|
|
254
|
-
rawErrors={rawErrors}
|
|
255
|
-
errorSchema={fieldErrorSchema}
|
|
256
|
-
value={selectedOption >= 0 ? selectedOption : undefined}
|
|
257
|
-
options={{ enumOptions, ...uiOptions }}
|
|
258
|
-
registry={registry}
|
|
259
|
-
placeholder={placeholder}
|
|
260
|
-
autocomplete={autocomplete}
|
|
261
|
-
autofocus={autofocus}
|
|
262
|
-
label={title ?? name}
|
|
263
|
-
hideLabel={!displayLabel}
|
|
264
|
-
readonly={readonly}
|
|
265
|
-
/>
|
|
266
|
-
) : undefined;
|
|
267
|
-
|
|
268
|
-
const optionsSchemaField =
|
|
269
|
-
(optionSchema && optionSchema.type !== 'null' && (
|
|
270
|
-
<SchemaFieldComponent {...this.props} schema={optionSchema} uiSchema={optionUiSchema} />
|
|
271
|
-
)) ||
|
|
272
|
-
null;
|
|
203
|
+
const translateEnum: TranslatableString = title
|
|
204
|
+
? TranslatableString.TitleOptionPrefix
|
|
205
|
+
: TranslatableString.OptionPrefix;
|
|
206
|
+
const translateParams = title ? [title] : [];
|
|
207
|
+
const enumOptions = retrievedOptions.map((opt: { title?: string }, index: number) => {
|
|
208
|
+
const { title: uiTitle = opt.title } = getUiOptions<T, S, F>(optionsUiSchema[index]);
|
|
209
|
+
return {
|
|
210
|
+
label: uiTitle || translateString(translateEnum, translateParams.concat(String(index + 1))),
|
|
211
|
+
value: index,
|
|
212
|
+
};
|
|
213
|
+
});
|
|
273
214
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
215
|
+
const selector =
|
|
216
|
+
!isOptionalRender || hasFormData ? (
|
|
217
|
+
<Widget
|
|
218
|
+
id={fieldId}
|
|
219
|
+
name={`${name}${schema.oneOf ? '__oneof_select' : '__anyof_select'}`}
|
|
220
|
+
schema={{ type: 'number', default: 0 } as S}
|
|
221
|
+
onChange={onOptionChange}
|
|
222
|
+
onBlur={onBlur}
|
|
223
|
+
onFocus={onFocus}
|
|
224
|
+
disabled={disabled || isEmpty(enumOptions)}
|
|
225
|
+
multiple={false}
|
|
226
|
+
rawErrors={rawErrors}
|
|
227
|
+
errorSchema={fieldErrorSchema}
|
|
228
|
+
value={selectedOption >= 0 ? selectedOption : undefined}
|
|
229
|
+
options={{ enumOptions, ...uiOptions }}
|
|
277
230
|
registry={registry}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
231
|
+
placeholder={placeholder}
|
|
232
|
+
autocomplete={autocomplete}
|
|
233
|
+
autofocus={autofocus}
|
|
234
|
+
label={title ?? name}
|
|
235
|
+
hideLabel={!displayLabel}
|
|
236
|
+
readonly={readonly}
|
|
281
237
|
/>
|
|
282
|
-
);
|
|
283
|
-
|
|
238
|
+
) : undefined;
|
|
239
|
+
|
|
240
|
+
const optionsSchemaField =
|
|
241
|
+
(optionSchema && optionSchema.type !== 'null' && (
|
|
242
|
+
<SchemaFieldComponent {...props} schema={optionSchema} uiSchema={optionUiSchema} />
|
|
243
|
+
)) ||
|
|
244
|
+
null;
|
|
245
|
+
|
|
246
|
+
return (
|
|
247
|
+
<MultiSchemaFieldTemplate
|
|
248
|
+
schema={schema}
|
|
249
|
+
registry={registry}
|
|
250
|
+
uiSchema={uiSchema}
|
|
251
|
+
selector={selector}
|
|
252
|
+
optionSchemaField={optionsSchemaField}
|
|
253
|
+
/>
|
|
254
|
+
);
|
|
284
255
|
}
|
|
285
256
|
|
|
286
257
|
export default AnyOfField;
|
|
@@ -64,7 +64,7 @@ function NumberField<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends
|
|
|
64
64
|
// <select>, due to an enum declaration etc) then, if the value ends in a
|
|
65
65
|
// trailing decimal point or multiple zeroes, strip the trailing values
|
|
66
66
|
const processed =
|
|
67
|
-
typeof normalizedValue === 'string' &&
|
|
67
|
+
typeof normalizedValue === 'string' && trailingCharMatcherWithPrefix.exec(normalizedValue)
|
|
68
68
|
? asNumber(normalizedValue.replace(trailingCharMatcher, ''))
|
|
69
69
|
: asNumber(normalizedValue);
|
|
70
70
|
|
|
@@ -8,6 +8,7 @@ import type {
|
|
|
8
8
|
FormContextType,
|
|
9
9
|
GenericObjectType,
|
|
10
10
|
Registry,
|
|
11
|
+
RJSFMarkedSchema,
|
|
11
12
|
RJSFSchema,
|
|
12
13
|
StrictRJSFSchema,
|
|
13
14
|
} from '@rjsf/utils';
|
|
@@ -291,7 +292,7 @@ export default function ObjectField<T = any, S extends StrictRJSFSchema = RJSFSc
|
|
|
291
292
|
apSchema = schemaUtils.retrieveSchema({ [REF_KEY]: apSchema[REF_KEY] } as S, formData);
|
|
292
293
|
type = apSchema.type;
|
|
293
294
|
constValue = apSchema.const;
|
|
294
|
-
defaultValue = apSchema
|
|
295
|
+
defaultValue = schemaUtils.getDefaultFormState(apSchema as S, defaultValue as T) as RJSFSchema['default'];
|
|
295
296
|
}
|
|
296
297
|
if (!type && (ANY_OF_KEY in apSchema || ONE_OF_KEY in apSchema)) {
|
|
297
298
|
type = 'object';
|
|
@@ -394,7 +395,9 @@ export default function ObjectField<T = any, S extends StrictRJSFSchema = RJSFSc
|
|
|
394
395
|
title: uiOptions.label === false ? '' : templateTitle,
|
|
395
396
|
description: uiOptions.label === false ? undefined : description,
|
|
396
397
|
properties: orderedProperties.map((propertyName) => {
|
|
397
|
-
const addedByAdditionalProperties =
|
|
398
|
+
const addedByAdditionalProperties = Boolean(
|
|
399
|
+
(schema.properties?.[propertyName] as RJSFMarkedSchema)?.[ADDITIONAL_PROPERTY_FLAG],
|
|
400
|
+
);
|
|
398
401
|
const fieldUiSchema = addedByAdditionalProperties ? uiSchema.additionalProperties : uiSchema[propertyName];
|
|
399
402
|
const hidden = getUiOptions<T, S, F>(fieldUiSchema).widget === 'hidden';
|
|
400
403
|
const content = (
|