@springmicro/forms 0.6.4 → 0.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/.eslintrc.cjs +22 -22
- package/README.md +11 -11
- package/dist/index.d.ts +0 -0
- package/dist/index.js +0 -0
- package/dist/index.umd.cjs +0 -0
- package/package.json +3 -3
- package/src/builder/bottom-drawer.tsx +429 -429
- package/src/builder/form-builder.tsx +256 -256
- package/src/builder/modal.tsx +39 -39
- package/src/builder/nodes/node-base.tsx +94 -94
- package/src/builder/nodes/node-child-helpers.tsx +273 -273
- package/src/builder/nodes/node-parent.tsx +187 -187
- package/src/builder/nodes/node-types/array-node.tsx +134 -134
- package/src/builder/nodes/node-types/date-node.tsx +60 -60
- package/src/builder/nodes/node-types/file-node.tsx +67 -67
- package/src/builder/nodes/node-types/integer-node.tsx +60 -60
- package/src/builder/nodes/node-types/object-node.tsx +67 -67
- package/src/builder/nodes/node-types/text-node.tsx +66 -66
- package/src/fields/ArrayField.tsx +875 -875
- package/src/fields/BooleanField.tsx +110 -110
- package/src/fields/MultiSchemaField.tsx +236 -236
- package/src/fields/NullField.tsx +22 -22
- package/src/fields/NumberField.tsx +87 -87
- package/src/fields/ObjectField.tsx +338 -338
- package/src/fields/SchemaField.tsx +402 -402
- package/src/fields/StringField.tsx +67 -67
- package/src/fields/index.ts +24 -24
- package/src/index.tsx +26 -26
- package/src/interfaces/MessagesProps.interface.ts +5 -5
- package/src/interfaces/Option.interface.ts +4 -4
- package/src/styles/select.styles.ts +28 -28
- package/src/templates/ArrayFieldDescriptionTemplate.tsx +42 -42
- package/src/templates/ArrayFieldItemTemplate.tsx +78 -78
- package/src/templates/ArrayFieldTemplate.tsx +90 -90
- package/src/templates/ArrayFieldTitleTemplate.tsx +44 -44
- package/src/templates/BaseInputTemplate.tsx +94 -94
- package/src/templates/ButtonTemplates/AddButton.tsx +29 -29
- package/src/templates/ButtonTemplates/IconButton.tsx +49 -49
- package/src/templates/ButtonTemplates/SubmitButton.tsx +29 -29
- package/src/templates/ButtonTemplates/index.ts +16 -16
- package/src/templates/DescriptionField.tsx +29 -29
- package/src/templates/ErrorList.tsx +25 -25
- package/src/templates/FieldTemplate/FieldTemplate.tsx +39 -39
- package/src/templates/FieldTemplate/Label.tsx +29 -29
- package/src/templates/FieldTemplate/WrapIfAdditional.tsx +85 -85
- package/src/templates/FieldTemplate/index.ts +3 -3
- package/src/templates/ObjectFieldTemplate.tsx +79 -79
- package/src/templates/TitleField.tsx +20 -20
- package/src/templates/UnsupportedField.tsx +29 -29
- package/src/templates/index.ts +32 -32
- package/src/types/Message.type.ts +6 -6
- package/src/types/RawMessage.type.ts +15 -15
- package/src/types/form-builder.ts +135 -135
- package/src/types/utils.type.ts +1 -1
- package/src/utils/form-builder.ts +424 -424
- package/src/utils/processSelectValue.ts +50 -50
- package/src/widgets/AltDateTimeWidget.tsx +17 -17
- package/src/widgets/AltDateWidget.tsx +216 -216
- package/src/widgets/CheckboxWidget.tsx +80 -80
- package/src/widgets/CheckboxesWidget.tsx +74 -74
- package/src/widgets/ColorWidget.tsx +26 -26
- package/src/widgets/DateTimeWidget.tsx +28 -28
- package/src/widgets/DateWidget.tsx +36 -36
- package/src/widgets/EmailWidget.tsx +19 -19
- package/src/widgets/FileWidget.tsx +144 -144
- package/src/widgets/HiddenWidget.tsx +22 -22
- package/src/widgets/PasswordWidget.tsx +20 -20
- package/src/widgets/RadioWidget.tsx +87 -87
- package/src/widgets/RangeWidget.tsx +24 -24
- package/src/widgets/SelectWidget.tsx +99 -99
- package/src/widgets/TextWidget.tsx +19 -19
- package/src/widgets/TextareaWidget.tsx +64 -64
- package/src/widgets/URLWidget.tsx +19 -19
- package/src/widgets/UpDownWidget.tsx +20 -20
- package/src/widgets/index.ts +43 -43
- package/tsconfig.json +24 -24
- package/tsconfig.node.json +10 -10
- package/vite.config.ts +25 -25
|
@@ -1,402 +1,402 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import type {
|
|
3
|
-
FieldProps,
|
|
4
|
-
FieldTemplateProps,
|
|
5
|
-
IdSchema,
|
|
6
|
-
Registry,
|
|
7
|
-
RJSFSchema,
|
|
8
|
-
UIOptionsType,
|
|
9
|
-
GenericObjectType,
|
|
10
|
-
} from "@rjsf/utils";
|
|
11
|
-
import {
|
|
12
|
-
mergeObjects,
|
|
13
|
-
deepEquals,
|
|
14
|
-
getUiOptions,
|
|
15
|
-
getSchemaType,
|
|
16
|
-
getTemplate,
|
|
17
|
-
ID_KEY,
|
|
18
|
-
} from "@rjsf/utils";
|
|
19
|
-
import isObject from "lodash/isObject";
|
|
20
|
-
import omit from "lodash/omit";
|
|
21
|
-
|
|
22
|
-
/** The map of component type to FieldName */
|
|
23
|
-
const COMPONENT_TYPES: { [key: string]: string } = {
|
|
24
|
-
array: "ArrayField",
|
|
25
|
-
boolean: "BooleanField",
|
|
26
|
-
integer: "NumberField",
|
|
27
|
-
number: "NumberField",
|
|
28
|
-
object: "ObjectField",
|
|
29
|
-
string: "StringField",
|
|
30
|
-
null: "NullField",
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
/** Computes and returns which `Field` implementation to return in order to render the field represented by the
|
|
34
|
-
* `schema`. The `uiOptions` are used to alter what potential `Field` implementation is actually returned. If no
|
|
35
|
-
* appropriate `Field` implementation can be found then a wrapper around `UnsupportedFieldTemplate` is used.
|
|
36
|
-
*
|
|
37
|
-
* @param schema - The schema from which to obtain the type
|
|
38
|
-
* @param uiOptions - The UI Options that may affect the component decision
|
|
39
|
-
* @param idSchema - The id that is passed to the `UnsupportedFieldTemplate`
|
|
40
|
-
* @param registry - The registry from which fields and templates are obtained
|
|
41
|
-
* @returns - The `Field` component that is used to render the actual field data
|
|
42
|
-
*/
|
|
43
|
-
function getFieldComponent<T, F extends GenericObjectType = any>(
|
|
44
|
-
schema: F,
|
|
45
|
-
uiOptions: UIOptionsType<T, F>,
|
|
46
|
-
idSchema: IdSchema<T>,
|
|
47
|
-
registry: Registry<T, F>
|
|
48
|
-
) {
|
|
49
|
-
const field = uiOptions.field;
|
|
50
|
-
const { fields } = registry;
|
|
51
|
-
if (typeof field === "function") {
|
|
52
|
-
return field;
|
|
53
|
-
}
|
|
54
|
-
if (typeof field === "string" && field in fields) {
|
|
55
|
-
return fields[field];
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const schemaType = getSchemaType(schema);
|
|
59
|
-
const type: string = Array.isArray(schemaType)
|
|
60
|
-
? schemaType[0]
|
|
61
|
-
: schemaType || "";
|
|
62
|
-
const componentName = COMPONENT_TYPES[type];
|
|
63
|
-
|
|
64
|
-
// If the type is not defined and the schema uses 'anyOf' or 'oneOf', don't
|
|
65
|
-
// render a field and let the MultiSchemaField component handle the form display
|
|
66
|
-
if (!componentName && (schema.anyOf || schema.oneOf)) {
|
|
67
|
-
return () => null;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
return componentName in fields
|
|
71
|
-
? fields[componentName]
|
|
72
|
-
: () => {
|
|
73
|
-
const UnsupportedFieldTemplate = getTemplate<
|
|
74
|
-
"UnsupportedFieldTemplate",
|
|
75
|
-
T,
|
|
76
|
-
F
|
|
77
|
-
>("UnsupportedFieldTemplate", registry, uiOptions);
|
|
78
|
-
|
|
79
|
-
return (
|
|
80
|
-
<UnsupportedFieldTemplate
|
|
81
|
-
schema={schema}
|
|
82
|
-
idSchema={idSchema}
|
|
83
|
-
reason={`Unknown field type ${schema.type}`}
|
|
84
|
-
registry={registry}
|
|
85
|
-
/>
|
|
86
|
-
);
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/** The `Help` component renders any help desired for a field
|
|
91
|
-
*
|
|
92
|
-
* @param props - The id and help information to be rendered
|
|
93
|
-
*/
|
|
94
|
-
function Help(props: { id: string; help?: string | React.ReactElement }) {
|
|
95
|
-
const { id, help } = props;
|
|
96
|
-
if (!help) {
|
|
97
|
-
return null;
|
|
98
|
-
}
|
|
99
|
-
if (typeof help === "string") {
|
|
100
|
-
return (
|
|
101
|
-
<p id={id} className="help-block">
|
|
102
|
-
{help}
|
|
103
|
-
</p>
|
|
104
|
-
);
|
|
105
|
-
}
|
|
106
|
-
return (
|
|
107
|
-
<div id={id} className="help-block">
|
|
108
|
-
{help}
|
|
109
|
-
</div>
|
|
110
|
-
);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/** The `ErrorList` component renders the errors local to the particular field
|
|
114
|
-
*
|
|
115
|
-
* @param props - The list of errors to show
|
|
116
|
-
*/
|
|
117
|
-
function ErrorList(props: { errors?: string[] }) {
|
|
118
|
-
const { errors = [] } = props;
|
|
119
|
-
if (errors.length === 0) {
|
|
120
|
-
return null;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
return (
|
|
124
|
-
<div>
|
|
125
|
-
<ul className="error-detail bs-callout bs-callout-info">
|
|
126
|
-
{errors
|
|
127
|
-
.filter((elem) => !!elem)
|
|
128
|
-
.map((error, index) => {
|
|
129
|
-
return (
|
|
130
|
-
<li className="text-danger" key={index}>
|
|
131
|
-
{error}
|
|
132
|
-
</li>
|
|
133
|
-
);
|
|
134
|
-
})}
|
|
135
|
-
</ul>
|
|
136
|
-
</div>
|
|
137
|
-
);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/** The `SchemaFieldRender` component is the work-horse of react-jsonschema-form, determining what kind of real field to
|
|
141
|
-
* render based on the `schema`, `uiSchema` and all the other props. It also deals with rendering the `anyOf` and
|
|
142
|
-
* `oneOf` fields.
|
|
143
|
-
*
|
|
144
|
-
* @param props - The `FieldProps` for this component
|
|
145
|
-
*/
|
|
146
|
-
function SchemaFieldRender<T, F extends GenericObjectType = any>(
|
|
147
|
-
props: FieldProps<T, F>
|
|
148
|
-
) {
|
|
149
|
-
const {
|
|
150
|
-
schema: _schema,
|
|
151
|
-
idSchema: _idSchema,
|
|
152
|
-
uiSchema,
|
|
153
|
-
formData,
|
|
154
|
-
errorSchema,
|
|
155
|
-
idPrefix,
|
|
156
|
-
idSeparator,
|
|
157
|
-
name,
|
|
158
|
-
onChange,
|
|
159
|
-
onKeyChange,
|
|
160
|
-
onDropPropertyClick,
|
|
161
|
-
required,
|
|
162
|
-
registry,
|
|
163
|
-
wasPropertyKeyModified = false,
|
|
164
|
-
} = props;
|
|
165
|
-
const { formContext, schemaUtils } = registry;
|
|
166
|
-
const uiOptions = getUiOptions<T, F>(uiSchema);
|
|
167
|
-
const FieldTemplate = getTemplate<"FieldTemplate", T, F>(
|
|
168
|
-
"FieldTemplate",
|
|
169
|
-
registry,
|
|
170
|
-
uiOptions
|
|
171
|
-
);
|
|
172
|
-
const DescriptionFieldTemplate = getTemplate<
|
|
173
|
-
"DescriptionFieldTemplate",
|
|
174
|
-
T,
|
|
175
|
-
F
|
|
176
|
-
>("DescriptionFieldTemplate", registry, uiOptions);
|
|
177
|
-
const schema = schemaUtils.retrieveSchema(_schema, formData);
|
|
178
|
-
const idSchema = mergeObjects(
|
|
179
|
-
schemaUtils.toIdSchema(
|
|
180
|
-
schema,
|
|
181
|
-
_idSchema.$id,
|
|
182
|
-
formData,
|
|
183
|
-
idPrefix,
|
|
184
|
-
idSeparator
|
|
185
|
-
),
|
|
186
|
-
_idSchema
|
|
187
|
-
) as IdSchema<T>;
|
|
188
|
-
// @ts-ignore
|
|
189
|
-
const FieldComponent = getFieldComponent(
|
|
190
|
-
schema,
|
|
191
|
-
uiOptions,
|
|
192
|
-
idSchema,
|
|
193
|
-
registry
|
|
194
|
-
);
|
|
195
|
-
const disabled = Boolean(props.disabled || uiOptions.disabled);
|
|
196
|
-
const readonly = Boolean(
|
|
197
|
-
props.readonly ||
|
|
198
|
-
uiOptions.readonly ||
|
|
199
|
-
props.schema.readOnly ||
|
|
200
|
-
schema.readOnly
|
|
201
|
-
);
|
|
202
|
-
const uiSchemaHideError = uiOptions.hideError;
|
|
203
|
-
// Set hideError to the value provided in the uiSchema, otherwise stick with the prop to propagate to children
|
|
204
|
-
const hideError =
|
|
205
|
-
uiSchemaHideError === undefined
|
|
206
|
-
? props.hideError
|
|
207
|
-
: Boolean(uiSchemaHideError);
|
|
208
|
-
const autofocus = Boolean(props.autofocus || uiOptions.autofocus);
|
|
209
|
-
if (Object.keys(schema).length === 0) {
|
|
210
|
-
return null;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
const displayLabel = schemaUtils.getDisplayLabel(schema, uiSchema);
|
|
214
|
-
|
|
215
|
-
const { __errors, ...fieldErrorSchema } = errorSchema || {};
|
|
216
|
-
// See #439: uiSchema: Don't pass consumed class names to child components
|
|
217
|
-
const fieldUiSchema = omit(uiSchema, ["ui:classNames", "classNames"]);
|
|
218
|
-
if ("ui:options" in fieldUiSchema) {
|
|
219
|
-
fieldUiSchema["ui:options"] = omit(fieldUiSchema["ui:options"], [
|
|
220
|
-
"classNames",
|
|
221
|
-
]);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
const field = (
|
|
225
|
-
// @ts-ignore
|
|
226
|
-
<FieldComponent
|
|
227
|
-
{...props}
|
|
228
|
-
idSchema={idSchema}
|
|
229
|
-
schema={schema}
|
|
230
|
-
uiSchema={fieldUiSchema}
|
|
231
|
-
disabled={disabled}
|
|
232
|
-
readonly={readonly}
|
|
233
|
-
hideError={hideError}
|
|
234
|
-
autofocus={autofocus}
|
|
235
|
-
errorSchema={fieldErrorSchema}
|
|
236
|
-
formContext={formContext}
|
|
237
|
-
rawErrors={__errors}
|
|
238
|
-
/>
|
|
239
|
-
);
|
|
240
|
-
|
|
241
|
-
const id = idSchema[ID_KEY];
|
|
242
|
-
|
|
243
|
-
// If this schema has a title defined, but the user has set a new key/label, retain their input.
|
|
244
|
-
let label;
|
|
245
|
-
if (wasPropertyKeyModified) {
|
|
246
|
-
label = name;
|
|
247
|
-
} else {
|
|
248
|
-
label = uiOptions.title || props.schema.title || schema.title || name;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
const description =
|
|
252
|
-
uiOptions.description ||
|
|
253
|
-
props.schema.description ||
|
|
254
|
-
schema.description ||
|
|
255
|
-
"";
|
|
256
|
-
const errors = __errors;
|
|
257
|
-
const help = uiOptions.help;
|
|
258
|
-
const hidden = uiOptions.widget === "hidden";
|
|
259
|
-
|
|
260
|
-
const classNames = ["form-control", "mb-2", "field", `field-${schema.type}`];
|
|
261
|
-
if (!hideError && errors && errors.length > 0) {
|
|
262
|
-
classNames.push("field-error has-error has-danger");
|
|
263
|
-
}
|
|
264
|
-
if (uiSchema?.classNames) {
|
|
265
|
-
if (import.meta.env.NODE_ENV !== "production") {
|
|
266
|
-
console.warn(
|
|
267
|
-
"'uiSchema.classNames' is deprecated and may be removed in a major release; Use 'ui:classNames' instead."
|
|
268
|
-
);
|
|
269
|
-
}
|
|
270
|
-
classNames.push(uiSchema.classNames);
|
|
271
|
-
}
|
|
272
|
-
if (uiOptions.classNames) {
|
|
273
|
-
classNames.push(uiOptions.classNames);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
const fieldProps: Omit<FieldTemplateProps<T, F>, "children"> = {
|
|
277
|
-
description: (
|
|
278
|
-
<DescriptionFieldTemplate
|
|
279
|
-
schema={schema}
|
|
280
|
-
id={`${id}__description`}
|
|
281
|
-
description={description}
|
|
282
|
-
registry={registry}
|
|
283
|
-
/>
|
|
284
|
-
),
|
|
285
|
-
rawDescription: description,
|
|
286
|
-
help: <Help id={`${id}__help`} help={help} />,
|
|
287
|
-
rawHelp: typeof help === "string" ? help : undefined,
|
|
288
|
-
errors: hideError ? undefined : <ErrorList errors={errors} />,
|
|
289
|
-
rawErrors: hideError ? undefined : errors,
|
|
290
|
-
id,
|
|
291
|
-
label,
|
|
292
|
-
hidden,
|
|
293
|
-
onChange,
|
|
294
|
-
onKeyChange,
|
|
295
|
-
onDropPropertyClick,
|
|
296
|
-
required,
|
|
297
|
-
disabled,
|
|
298
|
-
readonly,
|
|
299
|
-
hideError,
|
|
300
|
-
displayLabel,
|
|
301
|
-
classNames: classNames.join(" ").trim(),
|
|
302
|
-
formContext,
|
|
303
|
-
formData,
|
|
304
|
-
schema,
|
|
305
|
-
uiSchema,
|
|
306
|
-
registry,
|
|
307
|
-
};
|
|
308
|
-
|
|
309
|
-
const _AnyOfField = registry.fields.AnyOfField;
|
|
310
|
-
const _OneOfField = registry.fields.OneOfField;
|
|
311
|
-
|
|
312
|
-
return (
|
|
313
|
-
<FieldTemplate {...fieldProps}>
|
|
314
|
-
<>
|
|
315
|
-
{field}
|
|
316
|
-
{/*
|
|
317
|
-
If the schema `anyOf` or 'oneOf' can be rendered as a select control, don't
|
|
318
|
-
render the selection and let `StringField` component handle
|
|
319
|
-
rendering
|
|
320
|
-
*/}
|
|
321
|
-
{schema.anyOf &&
|
|
322
|
-
!uiSchema?.["ui:field"] &&
|
|
323
|
-
!schemaUtils.isSelect(schema) && (
|
|
324
|
-
<_AnyOfField
|
|
325
|
-
name={name}
|
|
326
|
-
disabled={disabled}
|
|
327
|
-
readonly={readonly}
|
|
328
|
-
hideError={hideError}
|
|
329
|
-
errorSchema={errorSchema}
|
|
330
|
-
formData={formData}
|
|
331
|
-
formContext={formContext}
|
|
332
|
-
idPrefix={idPrefix}
|
|
333
|
-
idSchema={idSchema}
|
|
334
|
-
idSeparator={idSeparator}
|
|
335
|
-
onBlur={props.onBlur}
|
|
336
|
-
onChange={props.onChange}
|
|
337
|
-
onFocus={props.onFocus}
|
|
338
|
-
options={schema.anyOf.map((_schema: any) =>
|
|
339
|
-
schemaUtils.retrieveSchema(
|
|
340
|
-
// @ts-ignore
|
|
341
|
-
isObject(_schema) ? _schema : {},
|
|
342
|
-
formData
|
|
343
|
-
)
|
|
344
|
-
)}
|
|
345
|
-
baseType={schema.type}
|
|
346
|
-
registry={registry}
|
|
347
|
-
schema={schema}
|
|
348
|
-
uiSchema={uiSchema}
|
|
349
|
-
/>
|
|
350
|
-
)}
|
|
351
|
-
{schema.oneOf &&
|
|
352
|
-
!uiSchema?.["ui:field"] &&
|
|
353
|
-
!schemaUtils.isSelect(schema) && (
|
|
354
|
-
<_OneOfField
|
|
355
|
-
name={name}
|
|
356
|
-
disabled={disabled}
|
|
357
|
-
readonly={readonly}
|
|
358
|
-
hideError={hideError}
|
|
359
|
-
errorSchema={errorSchema}
|
|
360
|
-
formData={formData}
|
|
361
|
-
formContext={formContext}
|
|
362
|
-
idPrefix={idPrefix}
|
|
363
|
-
idSchema={idSchema}
|
|
364
|
-
idSeparator={idSeparator}
|
|
365
|
-
onBlur={props.onBlur}
|
|
366
|
-
onChange={props.onChange}
|
|
367
|
-
onFocus={props.onFocus}
|
|
368
|
-
options={schema.oneOf.map((_schema: any) =>
|
|
369
|
-
schemaUtils.retrieveSchema(
|
|
370
|
-
// @ts-ignore
|
|
371
|
-
isObject(_schema) ? _schema : {},
|
|
372
|
-
formData
|
|
373
|
-
)
|
|
374
|
-
)}
|
|
375
|
-
baseType={schema.type}
|
|
376
|
-
registry={registry}
|
|
377
|
-
schema={schema}
|
|
378
|
-
uiSchema={uiSchema}
|
|
379
|
-
/>
|
|
380
|
-
)}
|
|
381
|
-
</>
|
|
382
|
-
</FieldTemplate>
|
|
383
|
-
);
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
/** The `SchemaField` component determines whether it is necessary to rerender the component based on any props changes
|
|
387
|
-
* and if so, calls the `SchemaFieldRender` component with the props.
|
|
388
|
-
*/
|
|
389
|
-
class SchemaField<
|
|
390
|
-
T = any,
|
|
391
|
-
F extends GenericObjectType = any,
|
|
392
|
-
> extends React.Component<FieldProps<T, F>> {
|
|
393
|
-
shouldComponentUpdate(nextProps: Readonly<FieldProps<T, F>>) {
|
|
394
|
-
return !deepEquals(this.props, nextProps);
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
render() {
|
|
398
|
-
return <SchemaFieldRender<T, F> {...this.props} />;
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
export default SchemaField;
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type {
|
|
3
|
+
FieldProps,
|
|
4
|
+
FieldTemplateProps,
|
|
5
|
+
IdSchema,
|
|
6
|
+
Registry,
|
|
7
|
+
RJSFSchema,
|
|
8
|
+
UIOptionsType,
|
|
9
|
+
GenericObjectType,
|
|
10
|
+
} from "@rjsf/utils";
|
|
11
|
+
import {
|
|
12
|
+
mergeObjects,
|
|
13
|
+
deepEquals,
|
|
14
|
+
getUiOptions,
|
|
15
|
+
getSchemaType,
|
|
16
|
+
getTemplate,
|
|
17
|
+
ID_KEY,
|
|
18
|
+
} from "@rjsf/utils";
|
|
19
|
+
import isObject from "lodash/isObject";
|
|
20
|
+
import omit from "lodash/omit";
|
|
21
|
+
|
|
22
|
+
/** The map of component type to FieldName */
|
|
23
|
+
const COMPONENT_TYPES: { [key: string]: string } = {
|
|
24
|
+
array: "ArrayField",
|
|
25
|
+
boolean: "BooleanField",
|
|
26
|
+
integer: "NumberField",
|
|
27
|
+
number: "NumberField",
|
|
28
|
+
object: "ObjectField",
|
|
29
|
+
string: "StringField",
|
|
30
|
+
null: "NullField",
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/** Computes and returns which `Field` implementation to return in order to render the field represented by the
|
|
34
|
+
* `schema`. The `uiOptions` are used to alter what potential `Field` implementation is actually returned. If no
|
|
35
|
+
* appropriate `Field` implementation can be found then a wrapper around `UnsupportedFieldTemplate` is used.
|
|
36
|
+
*
|
|
37
|
+
* @param schema - The schema from which to obtain the type
|
|
38
|
+
* @param uiOptions - The UI Options that may affect the component decision
|
|
39
|
+
* @param idSchema - The id that is passed to the `UnsupportedFieldTemplate`
|
|
40
|
+
* @param registry - The registry from which fields and templates are obtained
|
|
41
|
+
* @returns - The `Field` component that is used to render the actual field data
|
|
42
|
+
*/
|
|
43
|
+
function getFieldComponent<T, F extends GenericObjectType = any>(
|
|
44
|
+
schema: F,
|
|
45
|
+
uiOptions: UIOptionsType<T, F>,
|
|
46
|
+
idSchema: IdSchema<T>,
|
|
47
|
+
registry: Registry<T, F>
|
|
48
|
+
) {
|
|
49
|
+
const field = uiOptions.field;
|
|
50
|
+
const { fields } = registry;
|
|
51
|
+
if (typeof field === "function") {
|
|
52
|
+
return field;
|
|
53
|
+
}
|
|
54
|
+
if (typeof field === "string" && field in fields) {
|
|
55
|
+
return fields[field];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const schemaType = getSchemaType(schema);
|
|
59
|
+
const type: string = Array.isArray(schemaType)
|
|
60
|
+
? schemaType[0]
|
|
61
|
+
: schemaType || "";
|
|
62
|
+
const componentName = COMPONENT_TYPES[type];
|
|
63
|
+
|
|
64
|
+
// If the type is not defined and the schema uses 'anyOf' or 'oneOf', don't
|
|
65
|
+
// render a field and let the MultiSchemaField component handle the form display
|
|
66
|
+
if (!componentName && (schema.anyOf || schema.oneOf)) {
|
|
67
|
+
return () => null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return componentName in fields
|
|
71
|
+
? fields[componentName]
|
|
72
|
+
: () => {
|
|
73
|
+
const UnsupportedFieldTemplate = getTemplate<
|
|
74
|
+
"UnsupportedFieldTemplate",
|
|
75
|
+
T,
|
|
76
|
+
F
|
|
77
|
+
>("UnsupportedFieldTemplate", registry, uiOptions);
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<UnsupportedFieldTemplate
|
|
81
|
+
schema={schema}
|
|
82
|
+
idSchema={idSchema}
|
|
83
|
+
reason={`Unknown field type ${schema.type}`}
|
|
84
|
+
registry={registry}
|
|
85
|
+
/>
|
|
86
|
+
);
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** The `Help` component renders any help desired for a field
|
|
91
|
+
*
|
|
92
|
+
* @param props - The id and help information to be rendered
|
|
93
|
+
*/
|
|
94
|
+
function Help(props: { id: string; help?: string | React.ReactElement }) {
|
|
95
|
+
const { id, help } = props;
|
|
96
|
+
if (!help) {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
if (typeof help === "string") {
|
|
100
|
+
return (
|
|
101
|
+
<p id={id} className="help-block">
|
|
102
|
+
{help}
|
|
103
|
+
</p>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
return (
|
|
107
|
+
<div id={id} className="help-block">
|
|
108
|
+
{help}
|
|
109
|
+
</div>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** The `ErrorList` component renders the errors local to the particular field
|
|
114
|
+
*
|
|
115
|
+
* @param props - The list of errors to show
|
|
116
|
+
*/
|
|
117
|
+
function ErrorList(props: { errors?: string[] }) {
|
|
118
|
+
const { errors = [] } = props;
|
|
119
|
+
if (errors.length === 0) {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return (
|
|
124
|
+
<div>
|
|
125
|
+
<ul className="error-detail bs-callout bs-callout-info">
|
|
126
|
+
{errors
|
|
127
|
+
.filter((elem) => !!elem)
|
|
128
|
+
.map((error, index) => {
|
|
129
|
+
return (
|
|
130
|
+
<li className="text-danger" key={index}>
|
|
131
|
+
{error}
|
|
132
|
+
</li>
|
|
133
|
+
);
|
|
134
|
+
})}
|
|
135
|
+
</ul>
|
|
136
|
+
</div>
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** The `SchemaFieldRender` component is the work-horse of react-jsonschema-form, determining what kind of real field to
|
|
141
|
+
* render based on the `schema`, `uiSchema` and all the other props. It also deals with rendering the `anyOf` and
|
|
142
|
+
* `oneOf` fields.
|
|
143
|
+
*
|
|
144
|
+
* @param props - The `FieldProps` for this component
|
|
145
|
+
*/
|
|
146
|
+
function SchemaFieldRender<T, F extends GenericObjectType = any>(
|
|
147
|
+
props: FieldProps<T, F>
|
|
148
|
+
) {
|
|
149
|
+
const {
|
|
150
|
+
schema: _schema,
|
|
151
|
+
idSchema: _idSchema,
|
|
152
|
+
uiSchema,
|
|
153
|
+
formData,
|
|
154
|
+
errorSchema,
|
|
155
|
+
idPrefix,
|
|
156
|
+
idSeparator,
|
|
157
|
+
name,
|
|
158
|
+
onChange,
|
|
159
|
+
onKeyChange,
|
|
160
|
+
onDropPropertyClick,
|
|
161
|
+
required,
|
|
162
|
+
registry,
|
|
163
|
+
wasPropertyKeyModified = false,
|
|
164
|
+
} = props;
|
|
165
|
+
const { formContext, schemaUtils } = registry;
|
|
166
|
+
const uiOptions = getUiOptions<T, F>(uiSchema);
|
|
167
|
+
const FieldTemplate = getTemplate<"FieldTemplate", T, F>(
|
|
168
|
+
"FieldTemplate",
|
|
169
|
+
registry,
|
|
170
|
+
uiOptions
|
|
171
|
+
);
|
|
172
|
+
const DescriptionFieldTemplate = getTemplate<
|
|
173
|
+
"DescriptionFieldTemplate",
|
|
174
|
+
T,
|
|
175
|
+
F
|
|
176
|
+
>("DescriptionFieldTemplate", registry, uiOptions);
|
|
177
|
+
const schema = schemaUtils.retrieveSchema(_schema, formData);
|
|
178
|
+
const idSchema = mergeObjects(
|
|
179
|
+
schemaUtils.toIdSchema(
|
|
180
|
+
schema,
|
|
181
|
+
_idSchema.$id,
|
|
182
|
+
formData,
|
|
183
|
+
idPrefix,
|
|
184
|
+
idSeparator
|
|
185
|
+
),
|
|
186
|
+
_idSchema
|
|
187
|
+
) as IdSchema<T>;
|
|
188
|
+
// @ts-ignore
|
|
189
|
+
const FieldComponent = getFieldComponent(
|
|
190
|
+
schema,
|
|
191
|
+
uiOptions,
|
|
192
|
+
idSchema,
|
|
193
|
+
registry
|
|
194
|
+
);
|
|
195
|
+
const disabled = Boolean(props.disabled || uiOptions.disabled);
|
|
196
|
+
const readonly = Boolean(
|
|
197
|
+
props.readonly ||
|
|
198
|
+
uiOptions.readonly ||
|
|
199
|
+
props.schema.readOnly ||
|
|
200
|
+
schema.readOnly
|
|
201
|
+
);
|
|
202
|
+
const uiSchemaHideError = uiOptions.hideError;
|
|
203
|
+
// Set hideError to the value provided in the uiSchema, otherwise stick with the prop to propagate to children
|
|
204
|
+
const hideError =
|
|
205
|
+
uiSchemaHideError === undefined
|
|
206
|
+
? props.hideError
|
|
207
|
+
: Boolean(uiSchemaHideError);
|
|
208
|
+
const autofocus = Boolean(props.autofocus || uiOptions.autofocus);
|
|
209
|
+
if (Object.keys(schema).length === 0) {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const displayLabel = schemaUtils.getDisplayLabel(schema, uiSchema);
|
|
214
|
+
|
|
215
|
+
const { __errors, ...fieldErrorSchema } = errorSchema || {};
|
|
216
|
+
// See #439: uiSchema: Don't pass consumed class names to child components
|
|
217
|
+
const fieldUiSchema = omit(uiSchema, ["ui:classNames", "classNames"]);
|
|
218
|
+
if ("ui:options" in fieldUiSchema) {
|
|
219
|
+
fieldUiSchema["ui:options"] = omit(fieldUiSchema["ui:options"], [
|
|
220
|
+
"classNames",
|
|
221
|
+
]);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const field = (
|
|
225
|
+
// @ts-ignore
|
|
226
|
+
<FieldComponent
|
|
227
|
+
{...props}
|
|
228
|
+
idSchema={idSchema}
|
|
229
|
+
schema={schema}
|
|
230
|
+
uiSchema={fieldUiSchema}
|
|
231
|
+
disabled={disabled}
|
|
232
|
+
readonly={readonly}
|
|
233
|
+
hideError={hideError}
|
|
234
|
+
autofocus={autofocus}
|
|
235
|
+
errorSchema={fieldErrorSchema}
|
|
236
|
+
formContext={formContext}
|
|
237
|
+
rawErrors={__errors}
|
|
238
|
+
/>
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
const id = idSchema[ID_KEY];
|
|
242
|
+
|
|
243
|
+
// If this schema has a title defined, but the user has set a new key/label, retain their input.
|
|
244
|
+
let label;
|
|
245
|
+
if (wasPropertyKeyModified) {
|
|
246
|
+
label = name;
|
|
247
|
+
} else {
|
|
248
|
+
label = uiOptions.title || props.schema.title || schema.title || name;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const description =
|
|
252
|
+
uiOptions.description ||
|
|
253
|
+
props.schema.description ||
|
|
254
|
+
schema.description ||
|
|
255
|
+
"";
|
|
256
|
+
const errors = __errors;
|
|
257
|
+
const help = uiOptions.help;
|
|
258
|
+
const hidden = uiOptions.widget === "hidden";
|
|
259
|
+
|
|
260
|
+
const classNames = ["form-control", "mb-2", "field", `field-${schema.type}`];
|
|
261
|
+
if (!hideError && errors && errors.length > 0) {
|
|
262
|
+
classNames.push("field-error has-error has-danger");
|
|
263
|
+
}
|
|
264
|
+
if (uiSchema?.classNames) {
|
|
265
|
+
if (import.meta.env.NODE_ENV !== "production") {
|
|
266
|
+
console.warn(
|
|
267
|
+
"'uiSchema.classNames' is deprecated and may be removed in a major release; Use 'ui:classNames' instead."
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
classNames.push(uiSchema.classNames);
|
|
271
|
+
}
|
|
272
|
+
if (uiOptions.classNames) {
|
|
273
|
+
classNames.push(uiOptions.classNames);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const fieldProps: Omit<FieldTemplateProps<T, F>, "children"> = {
|
|
277
|
+
description: (
|
|
278
|
+
<DescriptionFieldTemplate
|
|
279
|
+
schema={schema}
|
|
280
|
+
id={`${id}__description`}
|
|
281
|
+
description={description}
|
|
282
|
+
registry={registry}
|
|
283
|
+
/>
|
|
284
|
+
),
|
|
285
|
+
rawDescription: description,
|
|
286
|
+
help: <Help id={`${id}__help`} help={help} />,
|
|
287
|
+
rawHelp: typeof help === "string" ? help : undefined,
|
|
288
|
+
errors: hideError ? undefined : <ErrorList errors={errors} />,
|
|
289
|
+
rawErrors: hideError ? undefined : errors,
|
|
290
|
+
id,
|
|
291
|
+
label,
|
|
292
|
+
hidden,
|
|
293
|
+
onChange,
|
|
294
|
+
onKeyChange,
|
|
295
|
+
onDropPropertyClick,
|
|
296
|
+
required,
|
|
297
|
+
disabled,
|
|
298
|
+
readonly,
|
|
299
|
+
hideError,
|
|
300
|
+
displayLabel,
|
|
301
|
+
classNames: classNames.join(" ").trim(),
|
|
302
|
+
formContext,
|
|
303
|
+
formData,
|
|
304
|
+
schema,
|
|
305
|
+
uiSchema,
|
|
306
|
+
registry,
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
const _AnyOfField = registry.fields.AnyOfField;
|
|
310
|
+
const _OneOfField = registry.fields.OneOfField;
|
|
311
|
+
|
|
312
|
+
return (
|
|
313
|
+
<FieldTemplate {...fieldProps}>
|
|
314
|
+
<>
|
|
315
|
+
{field}
|
|
316
|
+
{/*
|
|
317
|
+
If the schema `anyOf` or 'oneOf' can be rendered as a select control, don't
|
|
318
|
+
render the selection and let `StringField` component handle
|
|
319
|
+
rendering
|
|
320
|
+
*/}
|
|
321
|
+
{schema.anyOf &&
|
|
322
|
+
!uiSchema?.["ui:field"] &&
|
|
323
|
+
!schemaUtils.isSelect(schema) && (
|
|
324
|
+
<_AnyOfField
|
|
325
|
+
name={name}
|
|
326
|
+
disabled={disabled}
|
|
327
|
+
readonly={readonly}
|
|
328
|
+
hideError={hideError}
|
|
329
|
+
errorSchema={errorSchema}
|
|
330
|
+
formData={formData}
|
|
331
|
+
formContext={formContext}
|
|
332
|
+
idPrefix={idPrefix}
|
|
333
|
+
idSchema={idSchema}
|
|
334
|
+
idSeparator={idSeparator}
|
|
335
|
+
onBlur={props.onBlur}
|
|
336
|
+
onChange={props.onChange}
|
|
337
|
+
onFocus={props.onFocus}
|
|
338
|
+
options={schema.anyOf.map((_schema: any) =>
|
|
339
|
+
schemaUtils.retrieveSchema(
|
|
340
|
+
// @ts-ignore
|
|
341
|
+
isObject(_schema) ? _schema : {},
|
|
342
|
+
formData
|
|
343
|
+
)
|
|
344
|
+
)}
|
|
345
|
+
baseType={schema.type}
|
|
346
|
+
registry={registry}
|
|
347
|
+
schema={schema}
|
|
348
|
+
uiSchema={uiSchema}
|
|
349
|
+
/>
|
|
350
|
+
)}
|
|
351
|
+
{schema.oneOf &&
|
|
352
|
+
!uiSchema?.["ui:field"] &&
|
|
353
|
+
!schemaUtils.isSelect(schema) && (
|
|
354
|
+
<_OneOfField
|
|
355
|
+
name={name}
|
|
356
|
+
disabled={disabled}
|
|
357
|
+
readonly={readonly}
|
|
358
|
+
hideError={hideError}
|
|
359
|
+
errorSchema={errorSchema}
|
|
360
|
+
formData={formData}
|
|
361
|
+
formContext={formContext}
|
|
362
|
+
idPrefix={idPrefix}
|
|
363
|
+
idSchema={idSchema}
|
|
364
|
+
idSeparator={idSeparator}
|
|
365
|
+
onBlur={props.onBlur}
|
|
366
|
+
onChange={props.onChange}
|
|
367
|
+
onFocus={props.onFocus}
|
|
368
|
+
options={schema.oneOf.map((_schema: any) =>
|
|
369
|
+
schemaUtils.retrieveSchema(
|
|
370
|
+
// @ts-ignore
|
|
371
|
+
isObject(_schema) ? _schema : {},
|
|
372
|
+
formData
|
|
373
|
+
)
|
|
374
|
+
)}
|
|
375
|
+
baseType={schema.type}
|
|
376
|
+
registry={registry}
|
|
377
|
+
schema={schema}
|
|
378
|
+
uiSchema={uiSchema}
|
|
379
|
+
/>
|
|
380
|
+
)}
|
|
381
|
+
</>
|
|
382
|
+
</FieldTemplate>
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/** The `SchemaField` component determines whether it is necessary to rerender the component based on any props changes
|
|
387
|
+
* and if so, calls the `SchemaFieldRender` component with the props.
|
|
388
|
+
*/
|
|
389
|
+
class SchemaField<
|
|
390
|
+
T = any,
|
|
391
|
+
F extends GenericObjectType = any,
|
|
392
|
+
> extends React.Component<FieldProps<T, F>> {
|
|
393
|
+
shouldComponentUpdate(nextProps: Readonly<FieldProps<T, F>>) {
|
|
394
|
+
return !deepEquals(this.props, nextProps);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
render() {
|
|
398
|
+
return <SchemaFieldRender<T, F> {...this.props} />;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
export default SchemaField;
|