@rjsf/core
Version:
A simple React component capable of building HTML forms out of a JSON schema.
1,498 lines (1,491 loc) • 136 kB
JavaScript
// src/components/Form.tsx
import { Component as Component5, createRef } from "react";
import {
createSchemaUtils,
deepEquals as deepEquals3,
getChangedFields,
getTemplate as getTemplate20,
getUiOptions as getUiOptions12,
isObject as isObject5,
mergeObjects as mergeObjects2,
NAME_KEY,
RJSF_ADDITIONAL_PROPERTIES_FLAG,
shouldRender,
SUBMIT_BTN_OPTIONS_KEY,
toErrorList,
UI_GLOBAL_OPTIONS_KEY,
UI_OPTIONS_KEY as UI_OPTIONS_KEY2,
validationDataMerge,
createErrorHandler,
unwrapErrorHandler
} from "@rjsf/utils";
import _forEach from "lodash/forEach";
import _get from "lodash/get";
import _isEmpty from "lodash/isEmpty";
import _isNil from "lodash/isNil";
import _pick from "lodash/pick";
import _toPath from "lodash/toPath";
// src/getDefaultRegistry.ts
import { englishStringTranslator } from "@rjsf/utils";
// src/components/fields/ArrayField.tsx
import { Component } from "react";
import {
getTemplate,
getWidget,
getUiOptions,
isFixedItems,
allowAdditionalItems,
isCustomWidget,
optionsList,
TranslatableString,
ITEMS_KEY
} from "@rjsf/utils";
import cloneDeep from "lodash/cloneDeep";
import get from "lodash/get";
import isObject from "lodash/isObject";
import set from "lodash/set";
import uniqueId from "lodash/uniqueId";
import { jsx } from "react/jsx-runtime";
function generateRowId() {
return uniqueId("rjsf-array-item-");
}
function generateKeyedFormData(formData) {
return !Array.isArray(formData) ? [] : formData.map((item) => {
return {
key: generateRowId(),
item
};
});
}
function keyedToPlainFormData(keyedFormData) {
if (Array.isArray(keyedFormData)) {
return keyedFormData.map((keyedItem) => keyedItem.item);
}
return [];
}
var ArrayField = class extends Component {
/** Constructs an `ArrayField` from the `props`, generating the initial keyed data from the `formData`
*
* @param props - The `FieldProps` for this template
*/
constructor(props) {
super(props);
const { formData = [] } = props;
const keyedFormData = generateKeyedFormData(formData);
this.state = {
keyedFormData,
updatedKeyedFormData: false
};
}
/** React lifecycle method that is called when the props are about to change allowing the state to be updated. It
* regenerates the keyed form data and returns it
*
* @param nextProps - The next set of props data
* @param prevState - The previous set of state data
*/
static getDerivedStateFromProps(nextProps, prevState) {
if (prevState.updatedKeyedFormData) {
return {
updatedKeyedFormData: false
};
}
const nextFormData = Array.isArray(nextProps.formData) ? nextProps.formData : [];
const previousKeyedFormData = prevState.keyedFormData || [];
const newKeyedFormData = nextFormData.length === previousKeyedFormData.length ? previousKeyedFormData.map((previousKeyedFormDatum, index) => {
return {
key: previousKeyedFormDatum.key,
item: nextFormData[index]
};
}) : generateKeyedFormData(nextFormData);
return {
keyedFormData: newKeyedFormData
};
}
/** Returns the appropriate title for an item by getting first the title from the schema.items, then falling back to
* the description from the schema.items, and finally the string "Item"
*/
get itemTitle() {
const { schema, registry } = this.props;
const { translateString } = registry;
return get(
schema,
[ITEMS_KEY, "title"],
get(schema, [ITEMS_KEY, "description"], translateString(TranslatableString.ArrayItemTitle))
);
}
/** Determines whether the item described in the schema is always required, which is determined by whether any item
* may be null.
*
* @param itemSchema - The schema for the item
* @return - True if the item schema type does not contain the "null" type
*/
isItemRequired(itemSchema) {
if (Array.isArray(itemSchema.type)) {
return !itemSchema.type.includes("null");
}
return itemSchema.type !== "null";
}
/** Determines whether more items can be added to the array. If the uiSchema indicates the array doesn't allow adding
* then false is returned. Otherwise, if the schema indicates that there are a maximum number of items and the
* `formData` matches that value, then false is returned, otherwise true is returned.
*
* @param formItems - The list of items in the form
* @returns - True if the item is addable otherwise false
*/
canAddItem(formItems) {
const { schema, uiSchema, registry } = this.props;
let { addable } = getUiOptions(uiSchema, registry.globalUiOptions);
if (addable !== false) {
if (schema.maxItems !== void 0) {
addable = formItems.length < schema.maxItems;
} else {
addable = true;
}
}
return addable;
}
/** Returns the default form information for an item based on the schema for that item. Deals with the possibility
* that the schema is fixed and allows additional items.
*/
_getNewFormDataRow = () => {
const { schema, registry } = this.props;
const { schemaUtils } = registry;
let itemSchema = schema.items;
if (isFixedItems(schema) && allowAdditionalItems(schema)) {
itemSchema = schema.additionalItems;
}
return schemaUtils.getDefaultFormState(itemSchema);
};
/** Callback handler for when the user clicks on the add or add at index buttons. Creates a new row of keyed form data
* either at the end of the list (when index is not specified) or inserted at the `index` when it is, adding it into
* the state, and then returning `onChange()` with the plain form data converted from the keyed data
*
* @param event - The event for the click
* @param [index] - The optional index at which to add the new data
*/
_handleAddClick(event, index) {
if (event) {
event.preventDefault();
}
const { onChange, errorSchema } = this.props;
const { keyedFormData } = this.state;
let newErrorSchema;
if (errorSchema) {
newErrorSchema = {};
for (const idx in errorSchema) {
const i = parseInt(idx);
if (index === void 0 || i < index) {
set(newErrorSchema, [i], errorSchema[idx]);
} else if (i >= index) {
set(newErrorSchema, [i + 1], errorSchema[idx]);
}
}
}
const newKeyedFormDataRow = {
key: generateRowId(),
item: this._getNewFormDataRow()
};
const newKeyedFormData = [...keyedFormData];
if (index !== void 0) {
newKeyedFormData.splice(index, 0, newKeyedFormDataRow);
} else {
newKeyedFormData.push(newKeyedFormDataRow);
}
this.setState(
{
keyedFormData: newKeyedFormData,
updatedKeyedFormData: true
},
() => onChange(keyedToPlainFormData(newKeyedFormData), newErrorSchema)
);
}
/** Callback handler for when the user clicks on the add button. Creates a new row of keyed form data at the end of
* the list, adding it into the state, and then returning `onChange()` with the plain form data converted from the
* keyed data
*
* @param event - The event for the click
*/
onAddClick = (event) => {
this._handleAddClick(event);
};
/** Callback handler for when the user clicks on the add button on an existing array element. Creates a new row of
* keyed form data inserted at the `index`, adding it into the state, and then returning `onChange()` with the plain
* form data converted from the keyed data
*
* @param index - The index at which the add button is clicked
*/
onAddIndexClick = (index) => {
return (event) => {
this._handleAddClick(event, index);
};
};
/** Callback handler for when the user clicks on the copy button on an existing array element. Clones the row of
* keyed form data at the `index` into the next position in the state, and then returning `onChange()` with the plain
* form data converted from the keyed data
*
* @param index - The index at which the copy button is clicked
*/
onCopyIndexClick = (index) => {
return (event) => {
if (event) {
event.preventDefault();
}
const { onChange, errorSchema } = this.props;
const { keyedFormData } = this.state;
let newErrorSchema;
if (errorSchema) {
newErrorSchema = {};
for (const idx in errorSchema) {
const i = parseInt(idx);
if (i <= index) {
set(newErrorSchema, [i], errorSchema[idx]);
} else if (i > index) {
set(newErrorSchema, [i + 1], errorSchema[idx]);
}
}
}
const newKeyedFormDataRow = {
key: generateRowId(),
item: cloneDeep(keyedFormData[index].item)
};
const newKeyedFormData = [...keyedFormData];
if (index !== void 0) {
newKeyedFormData.splice(index + 1, 0, newKeyedFormDataRow);
} else {
newKeyedFormData.push(newKeyedFormDataRow);
}
this.setState(
{
keyedFormData: newKeyedFormData,
updatedKeyedFormData: true
},
() => onChange(keyedToPlainFormData(newKeyedFormData), newErrorSchema)
);
};
};
/** Callback handler for when the user clicks on the remove button on an existing array element. Removes the row of
* keyed form data at the `index` in the state, and then returning `onChange()` with the plain form data converted
* from the keyed data
*
* @param index - The index at which the remove button is clicked
*/
onDropIndexClick = (index) => {
return (event) => {
if (event) {
event.preventDefault();
}
const { onChange, errorSchema } = this.props;
const { keyedFormData } = this.state;
let newErrorSchema;
if (errorSchema) {
newErrorSchema = {};
for (const idx in errorSchema) {
const i = parseInt(idx);
if (i < index) {
set(newErrorSchema, [i], errorSchema[idx]);
} else if (i > index) {
set(newErrorSchema, [i - 1], errorSchema[idx]);
}
}
}
const newKeyedFormData = keyedFormData.filter((_, i) => i !== index);
this.setState(
{
keyedFormData: newKeyedFormData,
updatedKeyedFormData: true
},
() => onChange(keyedToPlainFormData(newKeyedFormData), newErrorSchema)
);
};
};
/** Callback handler for when the user clicks on one of the move item buttons on an existing array element. Moves the
* row of keyed form data at the `index` to the `newIndex` in the state, and then returning `onChange()` with the
* plain form data converted from the keyed data
*
* @param index - The index of the item to move
* @param newIndex - The index to where the item is to be moved
*/
onReorderClick = (index, newIndex) => {
return (event) => {
if (event) {
event.preventDefault();
event.currentTarget.blur();
}
const { onChange, errorSchema } = this.props;
let newErrorSchema;
if (errorSchema) {
newErrorSchema = {};
for (const idx in errorSchema) {
const i = parseInt(idx);
if (i == index) {
set(newErrorSchema, [newIndex], errorSchema[index]);
} else if (i == newIndex) {
set(newErrorSchema, [index], errorSchema[newIndex]);
} else {
set(newErrorSchema, [idx], errorSchema[i]);
}
}
}
const { keyedFormData } = this.state;
function reOrderArray() {
const _newKeyedFormData = keyedFormData.slice();
_newKeyedFormData.splice(index, 1);
_newKeyedFormData.splice(newIndex, 0, keyedFormData[index]);
return _newKeyedFormData;
}
const newKeyedFormData = reOrderArray();
this.setState(
{
keyedFormData: newKeyedFormData
},
() => onChange(keyedToPlainFormData(newKeyedFormData), newErrorSchema)
);
};
};
/** Callback handler used to deal with changing the value of the data in the array at the `index`. Calls the
* `onChange` callback with the updated form data
*
* @param index - The index of the item being changed
*/
onChangeForIndex = (index) => {
return (value, newErrorSchema, id) => {
const { formData, onChange, errorSchema } = this.props;
const arrayData = Array.isArray(formData) ? formData : [];
const newFormData = arrayData.map((item, i) => {
const jsonValue = typeof value === "undefined" ? null : value;
return index === i ? jsonValue : item;
});
onChange(
newFormData,
errorSchema && errorSchema && {
...errorSchema,
[index]: newErrorSchema
},
id
);
};
};
/** Callback handler used to change the value for a checkbox */
onSelectChange = (value) => {
const { onChange, idSchema } = this.props;
onChange(value, void 0, idSchema && idSchema.$id);
};
/** Renders the `ArrayField` depending on the specific needs of the schema and uischema elements
*/
render() {
const { schema, uiSchema, idSchema, registry } = this.props;
const { schemaUtils, translateString } = registry;
if (!(ITEMS_KEY in schema)) {
const uiOptions = getUiOptions(uiSchema);
const UnsupportedFieldTemplate = getTemplate(
"UnsupportedFieldTemplate",
registry,
uiOptions
);
return /* @__PURE__ */ jsx(
UnsupportedFieldTemplate,
{
schema,
idSchema,
reason: translateString(TranslatableString.MissingItems),
registry
}
);
}
if (schemaUtils.isMultiSelect(schema)) {
return this.renderMultiSelect();
}
if (isCustomWidget(uiSchema)) {
return this.renderCustomWidget();
}
if (isFixedItems(schema)) {
return this.renderFixedArray();
}
if (schemaUtils.isFilesArray(schema, uiSchema)) {
return this.renderFiles();
}
return this.renderNormalArray();
}
/** Renders a normal array without any limitations of length
*/
renderNormalArray() {
const {
schema,
uiSchema = {},
errorSchema,
idSchema,
name,
title,
disabled = false,
readonly = false,
autofocus = false,
required = false,
registry,
onBlur,
onFocus,
idPrefix,
idSeparator = "_",
rawErrors
} = this.props;
const { keyedFormData } = this.state;
const fieldTitle = schema.title || title || name;
const { schemaUtils, formContext } = registry;
const uiOptions = getUiOptions(uiSchema);
const _schemaItems = isObject(schema.items) ? schema.items : {};
const itemsSchema = schemaUtils.retrieveSchema(_schemaItems);
const formData = keyedToPlainFormData(this.state.keyedFormData);
const canAdd = this.canAddItem(formData);
const arrayProps = {
canAdd,
items: keyedFormData.map((keyedItem, index) => {
const { key, item } = keyedItem;
const itemCast = item;
const itemSchema = schemaUtils.retrieveSchema(_schemaItems, itemCast);
const itemErrorSchema = errorSchema ? errorSchema[index] : void 0;
const itemIdPrefix = idSchema.$id + idSeparator + index;
const itemIdSchema = schemaUtils.toIdSchema(itemSchema, itemIdPrefix, itemCast, idPrefix, idSeparator);
return this.renderArrayFieldItem({
key,
index,
name: name && `${name}-${index}`,
title: fieldTitle ? `${fieldTitle}-${index + 1}` : void 0,
canAdd,
canMoveUp: index > 0,
canMoveDown: index < formData.length - 1,
itemSchema,
itemIdSchema,
itemErrorSchema,
itemData: itemCast,
itemUiSchema: uiSchema.items,
autofocus: autofocus && index === 0,
onBlur,
onFocus,
rawErrors,
totalItems: keyedFormData.length
});
}),
className: `field field-array field-array-of-${itemsSchema.type}`,
disabled,
idSchema,
uiSchema,
onAddClick: this.onAddClick,
readonly,
required,
schema,
title: fieldTitle,
formContext,
formData,
rawErrors,
registry
};
const Template = getTemplate("ArrayFieldTemplate", registry, uiOptions);
return /* @__PURE__ */ jsx(Template, { ...arrayProps });
}
/** Renders an array using the custom widget provided by the user in the `uiSchema`
*/
renderCustomWidget() {
const {
schema,
idSchema,
uiSchema,
disabled = false,
readonly = false,
autofocus = false,
required = false,
hideError,
placeholder,
onBlur,
onFocus,
formData: items = [],
registry,
rawErrors,
name
} = this.props;
const { widgets: widgets2, formContext, globalUiOptions, schemaUtils } = registry;
const { widget, title: uiTitle, ...options } = getUiOptions(uiSchema, globalUiOptions);
const Widget = getWidget(schema, widget, widgets2);
const label = uiTitle ?? schema.title ?? name;
const displayLabel = schemaUtils.getDisplayLabel(schema, uiSchema, globalUiOptions);
return /* @__PURE__ */ jsx(
Widget,
{
id: idSchema.$id,
name,
multiple: true,
onChange: this.onSelectChange,
onBlur,
onFocus,
options,
schema,
uiSchema,
registry,
value: items,
disabled,
readonly,
hideError,
required,
label,
hideLabel: !displayLabel,
placeholder,
formContext,
autofocus,
rawErrors
}
);
}
/** Renders an array as a set of checkboxes
*/
renderMultiSelect() {
const {
schema,
idSchema,
uiSchema,
formData: items = [],
disabled = false,
readonly = false,
autofocus = false,
required = false,
placeholder,
onBlur,
onFocus,
registry,
rawErrors,
name
} = this.props;
const { widgets: widgets2, schemaUtils, formContext, globalUiOptions } = registry;
const itemsSchema = schemaUtils.retrieveSchema(schema.items, items);
const enumOptions = optionsList(itemsSchema, uiSchema);
const { widget = "select", title: uiTitle, ...options } = getUiOptions(uiSchema, globalUiOptions);
const Widget = getWidget(schema, widget, widgets2);
const label = uiTitle ?? schema.title ?? name;
const displayLabel = schemaUtils.getDisplayLabel(schema, uiSchema, globalUiOptions);
return /* @__PURE__ */ jsx(
Widget,
{
id: idSchema.$id,
name,
multiple: true,
onChange: this.onSelectChange,
onBlur,
onFocus,
options: { ...options, enumOptions },
schema,
uiSchema,
registry,
value: items,
disabled,
readonly,
required,
label,
hideLabel: !displayLabel,
placeholder,
formContext,
autofocus,
rawErrors
}
);
}
/** Renders an array of files using the `FileWidget`
*/
renderFiles() {
const {
schema,
uiSchema,
idSchema,
name,
disabled = false,
readonly = false,
autofocus = false,
required = false,
onBlur,
onFocus,
registry,
formData: items = [],
rawErrors
} = this.props;
const { widgets: widgets2, formContext, globalUiOptions, schemaUtils } = registry;
const { widget = "files", title: uiTitle, ...options } = getUiOptions(uiSchema, globalUiOptions);
const Widget = getWidget(schema, widget, widgets2);
const label = uiTitle ?? schema.title ?? name;
const displayLabel = schemaUtils.getDisplayLabel(schema, uiSchema, globalUiOptions);
return /* @__PURE__ */ jsx(
Widget,
{
options,
id: idSchema.$id,
name,
multiple: true,
onChange: this.onSelectChange,
onBlur,
onFocus,
schema,
uiSchema,
value: items,
disabled,
readonly,
required,
registry,
formContext,
autofocus,
rawErrors,
label,
hideLabel: !displayLabel
}
);
}
/** Renders an array that has a maximum limit of items
*/
renderFixedArray() {
const {
schema,
uiSchema = {},
formData = [],
errorSchema,
idPrefix,
idSeparator = "_",
idSchema,
name,
title,
disabled = false,
readonly = false,
autofocus = false,
required = false,
registry,
onBlur,
onFocus,
rawErrors
} = this.props;
const { keyedFormData } = this.state;
let { formData: items = [] } = this.props;
const fieldTitle = schema.title || title || name;
const uiOptions = getUiOptions(uiSchema);
const { schemaUtils, formContext } = registry;
const _schemaItems = isObject(schema.items) ? schema.items : [];
const itemSchemas = _schemaItems.map(
(item, index) => schemaUtils.retrieveSchema(item, formData[index])
);
const additionalSchema = isObject(schema.additionalItems) ? schemaUtils.retrieveSchema(schema.additionalItems, formData) : null;
if (!items || items.length < itemSchemas.length) {
items = items || [];
items = items.concat(new Array(itemSchemas.length - items.length));
}
const canAdd = this.canAddItem(items) && !!additionalSchema;
const arrayProps = {
canAdd,
className: "field field-array field-array-fixed-items",
disabled,
idSchema,
formData,
items: keyedFormData.map((keyedItem, index) => {
const { key, item } = keyedItem;
const itemCast = item;
const additional = index >= itemSchemas.length;
const itemSchema = (additional && isObject(schema.additionalItems) ? schemaUtils.retrieveSchema(schema.additionalItems, itemCast) : itemSchemas[index]) || {};
const itemIdPrefix = idSchema.$id + idSeparator + index;
const itemIdSchema = schemaUtils.toIdSchema(itemSchema, itemIdPrefix, itemCast, idPrefix, idSeparator);
const itemUiSchema = additional ? uiSchema.additionalItems || {} : Array.isArray(uiSchema.items) ? uiSchema.items[index] : uiSchema.items || {};
const itemErrorSchema = errorSchema ? errorSchema[index] : void 0;
return this.renderArrayFieldItem({
key,
index,
name: name && `${name}-${index}`,
title: fieldTitle ? `${fieldTitle}-${index + 1}` : void 0,
canAdd,
canRemove: additional,
canMoveUp: index >= itemSchemas.length + 1,
canMoveDown: additional && index < items.length - 1,
itemSchema,
itemData: itemCast,
itemUiSchema,
itemIdSchema,
itemErrorSchema,
autofocus: autofocus && index === 0,
onBlur,
onFocus,
rawErrors,
totalItems: keyedFormData.length
});
}),
onAddClick: this.onAddClick,
readonly,
required,
registry,
schema,
uiSchema,
title: fieldTitle,
formContext,
errorSchema,
rawErrors
};
const Template = getTemplate("ArrayFieldTemplate", registry, uiOptions);
return /* @__PURE__ */ jsx(Template, { ...arrayProps });
}
/** Renders the individual array item using a `SchemaField` along with the additional properties required to be send
* back to the `ArrayFieldItemTemplate`.
*
* @param props - The props for the individual array item to be rendered
*/
renderArrayFieldItem(props) {
const {
key,
index,
name,
canAdd,
canRemove = true,
canMoveUp,
canMoveDown,
itemSchema,
itemData,
itemUiSchema,
itemIdSchema,
itemErrorSchema,
autofocus,
onBlur,
onFocus,
rawErrors,
totalItems,
title
} = props;
const { disabled, hideError, idPrefix, idSeparator, readonly, uiSchema, registry, formContext } = this.props;
const {
fields: { ArraySchemaField, SchemaField: SchemaField2 },
globalUiOptions
} = registry;
const ItemSchemaField = ArraySchemaField || SchemaField2;
const { orderable = true, removable = true, copyable = false } = getUiOptions(uiSchema, globalUiOptions);
const has2 = {
moveUp: orderable && canMoveUp,
moveDown: orderable && canMoveDown,
copy: copyable && canAdd,
remove: removable && canRemove,
toolbar: false
};
has2.toolbar = Object.keys(has2).some((key2) => has2[key2]);
return {
children: /* @__PURE__ */ jsx(
ItemSchemaField,
{
name,
title,
index,
schema: itemSchema,
uiSchema: itemUiSchema,
formData: itemData,
formContext,
errorSchema: itemErrorSchema,
idPrefix,
idSeparator,
idSchema: itemIdSchema,
required: this.isItemRequired(itemSchema),
onChange: this.onChangeForIndex(index),
onBlur,
onFocus,
registry,
disabled,
readonly,
hideError,
autofocus,
rawErrors
}
),
className: "array-item",
disabled,
canAdd,
hasCopy: has2.copy,
hasToolbar: has2.toolbar,
hasMoveUp: has2.moveUp,
hasMoveDown: has2.moveDown,
hasRemove: has2.remove,
index,
totalItems,
key,
onAddIndexClick: this.onAddIndexClick,
onCopyIndexClick: this.onCopyIndexClick,
onDropIndexClick: this.onDropIndexClick,
onReorderClick: this.onReorderClick,
readonly,
registry,
schema: itemSchema,
uiSchema: itemUiSchema
};
}
};
var ArrayField_default = ArrayField;
// src/components/fields/BooleanField.tsx
import {
getWidget as getWidget2,
getUiOptions as getUiOptions2,
optionsList as optionsList2,
TranslatableString as TranslatableString2
} from "@rjsf/utils";
import isObject2 from "lodash/isObject";
import { jsx as jsx2 } from "react/jsx-runtime";
function BooleanField(props) {
const {
schema,
name,
uiSchema,
idSchema,
formData,
registry,
required,
disabled,
readonly,
hideError,
autofocus,
title,
onChange,
onFocus,
onBlur,
rawErrors
} = props;
const { title: schemaTitle } = schema;
const { widgets: widgets2, formContext, translateString, globalUiOptions } = registry;
const {
widget = "checkbox",
title: uiTitle,
// Unlike the other fields, don't use `getDisplayLabel()` since it always returns false for the boolean type
label: displayLabel = true,
...options
} = getUiOptions2(uiSchema, globalUiOptions);
const Widget = getWidget2(schema, widget, widgets2);
const yes = translateString(TranslatableString2.YesLabel);
const no = translateString(TranslatableString2.NoLabel);
let enumOptions;
const label = uiTitle ?? schemaTitle ?? title ?? name;
if (Array.isArray(schema.oneOf)) {
enumOptions = optionsList2(
{
oneOf: schema.oneOf.map((option) => {
if (isObject2(option)) {
return {
...option,
title: option.title || (option.const === true ? yes : no)
};
}
return void 0;
}).filter((o) => o)
// cast away the error that typescript can't grok is fixed
},
uiSchema
);
} else {
const schemaWithEnumNames = schema;
const enums = schema.enum ?? [true, false];
if (!schemaWithEnumNames.enumNames && enums.length === 2 && enums.every((v) => typeof v === "boolean")) {
enumOptions = [
{
value: enums[0],
label: enums[0] ? yes : no
},
{
value: enums[1],
label: enums[1] ? yes : no
}
];
} else {
enumOptions = optionsList2(
{
enum: enums,
// NOTE: enumNames is deprecated, but still supported for now.
enumNames: schemaWithEnumNames.enumNames
},
uiSchema
);
}
}
return /* @__PURE__ */ jsx2(
Widget,
{
options: { ...options, enumOptions },
schema,
uiSchema,
id: idSchema.$id,
name,
onChange,
onFocus,
onBlur,
label,
hideLabel: !displayLabel,
value: formData,
required,
disabled,
readonly,
hideError,
registry,
formContext,
autofocus,
rawErrors
}
);
}
var BooleanField_default = BooleanField;
// src/components/fields/MultiSchemaField.tsx
import { Component as Component2 } from "react";
import get2 from "lodash/get";
import isEmpty from "lodash/isEmpty";
import omit from "lodash/omit";
import {
ANY_OF_KEY,
deepEquals,
ERRORS_KEY,
getDiscriminatorFieldFromSchema,
getUiOptions as getUiOptions3,
getWidget as getWidget3,
mergeSchemas,
ONE_OF_KEY,
TranslatableString as TranslatableString3
} from "@rjsf/utils";
import { jsx as jsx3, jsxs } from "react/jsx-runtime";
var AnyOfField = class extends Component2 {
/** Constructs an `AnyOfField` with the given `props` to initialize the initially selected option in state
*
* @param props - The `FieldProps` for this template
*/
constructor(props) {
super(props);
const {
formData,
options,
registry: { schemaUtils }
} = this.props;
const retrievedOptions = options.map((opt) => schemaUtils.retrieveSchema(opt, formData));
this.state = {
retrievedOptions,
selectedOption: this.getMatchingOption(0, formData, retrievedOptions)
};
}
/** React lifecycle method that is called when the props and/or state for this component is updated. It recomputes the
* currently selected option based on the overall `formData`
*
* @param prevProps - The previous `FieldProps` for this template
* @param prevState - The previous `AnyOfFieldState` for this template
*/
componentDidUpdate(prevProps, prevState) {
const { formData, options, idSchema } = this.props;
const { selectedOption } = this.state;
let newState = this.state;
if (!deepEquals(prevProps.options, options)) {
const {
registry: { schemaUtils }
} = this.props;
const retrievedOptions = options.map((opt) => schemaUtils.retrieveSchema(opt, formData));
newState = { selectedOption, retrievedOptions };
}
if (!deepEquals(formData, prevProps.formData) && idSchema.$id === prevProps.idSchema.$id) {
const { retrievedOptions } = newState;
const matchingOption = this.getMatchingOption(selectedOption, formData, retrievedOptions);
if (prevState && matchingOption !== selectedOption) {
newState = { selectedOption: matchingOption, retrievedOptions };
}
}
if (newState !== this.state) {
this.setState(newState);
}
}
/** Determines the best matching option for the given `formData` and `options`.
*
* @param formData - The new formData
* @param options - The list of options to choose from
* @return - The index of the `option` that best matches the `formData`
*/
getMatchingOption(selectedOption, formData, options) {
const {
schema,
registry: { schemaUtils }
} = this.props;
const discriminator = getDiscriminatorFieldFromSchema(schema);
const option = schemaUtils.getClosestMatchingOption(formData, options, selectedOption, discriminator);
return option;
}
/** Callback handler to remember what the currently selected option is. In addition to that the `formData` is updated
* to remove properties that are not part of the newly selected option schema, and then the updated data is passed to
* the `onChange` handler.
*
* @param option - The new option value being selected
*/
onOptionChange = (option) => {
const { selectedOption, retrievedOptions } = this.state;
const { formData, onChange, registry } = this.props;
const { schemaUtils } = registry;
const intOption = option !== void 0 ? parseInt(option, 10) : -1;
if (intOption === selectedOption) {
return;
}
const newOption = intOption >= 0 ? retrievedOptions[intOption] : void 0;
const oldOption = selectedOption >= 0 ? retrievedOptions[selectedOption] : void 0;
let newFormData = schemaUtils.sanitizeDataForNewSchema(newOption, oldOption, formData);
if (newOption) {
newFormData = schemaUtils.getDefaultFormState(newOption, newFormData, "excludeObjectChildren");
}
this.setState({ selectedOption: intOption }, () => {
onChange(newFormData, void 0, this.getFieldId());
});
};
getFieldId() {
const { idSchema, schema } = this.props;
return `${idSchema.$id}${schema.oneOf ? "__oneof_select" : "__anyof_select"}`;
}
/** Renders the `AnyOfField` selector along with a `SchemaField` for the value of the `formData`
*/
render() {
const {
name,
disabled = false,
errorSchema = {},
formContext,
onBlur,
onFocus,
readonly,
registry,
schema,
uiSchema
} = this.props;
const { widgets: widgets2, fields: fields2, translateString, globalUiOptions, schemaUtils } = registry;
const { SchemaField: _SchemaField } = fields2;
const { selectedOption, retrievedOptions } = this.state;
const {
widget = "select",
placeholder,
autofocus,
autocomplete,
title = schema.title,
...uiOptions
} = getUiOptions3(uiSchema, globalUiOptions);
const Widget = getWidget3({ type: "number" }, widget, widgets2);
const rawErrors = get2(errorSchema, ERRORS_KEY, []);
const fieldErrorSchema = omit(errorSchema, [ERRORS_KEY]);
const displayLabel = schemaUtils.getDisplayLabel(schema, uiSchema, globalUiOptions);
const option = selectedOption >= 0 ? retrievedOptions[selectedOption] || null : null;
let optionSchema;
if (option) {
const { required } = schema;
optionSchema = required ? mergeSchemas({ required }, option) : option;
}
let optionsUiSchema = [];
if (ONE_OF_KEY in schema && uiSchema && ONE_OF_KEY in uiSchema) {
if (Array.isArray(uiSchema[ONE_OF_KEY])) {
optionsUiSchema = uiSchema[ONE_OF_KEY];
} else {
console.warn(`uiSchema.oneOf is not an array for "${title || name}"`);
}
} else if (ANY_OF_KEY in schema && uiSchema && ANY_OF_KEY in uiSchema) {
if (Array.isArray(uiSchema[ANY_OF_KEY])) {
optionsUiSchema = uiSchema[ANY_OF_KEY];
} else {
console.warn(`uiSchema.anyOf is not an array for "${title || name}"`);
}
}
let optionUiSchema = uiSchema;
if (selectedOption >= 0 && optionsUiSchema.length > selectedOption) {
optionUiSchema = optionsUiSchema[selectedOption];
}
const translateEnum = title ? TranslatableString3.TitleOptionPrefix : TranslatableString3.OptionPrefix;
const translateParams = title ? [title] : [];
const enumOptions = retrievedOptions.map((opt, index) => {
const { title: uiTitle = opt.title } = getUiOptions3(optionsUiSchema[index]);
return {
label: uiTitle || translateString(translateEnum, translateParams.concat(String(index + 1))),
value: index
};
});
return /* @__PURE__ */ jsxs("div", { className: "panel panel-default panel-body", children: [
/* @__PURE__ */ jsx3("div", { className: "form-group", children: /* @__PURE__ */ jsx3(
Widget,
{
id: this.getFieldId(),
name: `${name}${schema.oneOf ? "__oneof_select" : "__anyof_select"}`,
schema: { type: "number", default: 0 },
onChange: this.onOptionChange,
onBlur,
onFocus,
disabled: disabled || isEmpty(enumOptions),
multiple: false,
rawErrors,
errorSchema: fieldErrorSchema,
value: selectedOption >= 0 ? selectedOption : void 0,
options: { enumOptions, ...uiOptions },
registry,
formContext,
placeholder,
autocomplete,
autofocus,
label: title ?? name,
hideLabel: !displayLabel,
readonly
}
) }),
optionSchema && optionSchema.type !== "null" && /* @__PURE__ */ jsx3(_SchemaField, { ...this.props, schema: optionSchema, uiSchema: optionUiSchema })
] });
}
};
var MultiSchemaField_default = AnyOfField;
// src/components/fields/NumberField.tsx
import { useState, useCallback } from "react";
import { asNumber } from "@rjsf/utils";
import { jsx as jsx4 } from "react/jsx-runtime";
var trailingCharMatcherWithPrefix = /\.([0-9]*0)*$/;
var trailingCharMatcher = /[0.]0*$/;
function NumberField(props) {
const { registry, onChange, formData, value: initialValue } = props;
const [lastValue, setLastValue] = useState(initialValue);
const { StringField: StringField2 } = registry.fields;
let value = formData;
const handleChange = useCallback(
(value2, errorSchema, id) => {
setLastValue(value2);
if (`${value2}`.charAt(0) === ".") {
value2 = `0${value2}`;
}
const processed = typeof value2 === "string" && value2.match(trailingCharMatcherWithPrefix) ? asNumber(value2.replace(trailingCharMatcher, "")) : asNumber(value2);
onChange(processed, errorSchema, id);
},
[onChange]
);
if (typeof lastValue === "string" && typeof value === "number") {
const re = new RegExp(`^(${String(value).replace(".", "\\.")})?\\.?0*$`);
if (lastValue.match(re)) {
value = lastValue;
}
}
return /* @__PURE__ */ jsx4(StringField2, { ...props, formData: value, onChange: handleChange });
}
var NumberField_default = NumberField;
// src/components/fields/ObjectField.tsx
import { Component as Component3 } from "react";
import {
getTemplate as getTemplate2,
getUiOptions as getUiOptions4,
orderProperties,
TranslatableString as TranslatableString4,
ADDITIONAL_PROPERTY_FLAG,
PROPERTIES_KEY,
REF_KEY,
ANY_OF_KEY as ANY_OF_KEY2,
ONE_OF_KEY as ONE_OF_KEY2
} from "@rjsf/utils";
import Markdown from "markdown-to-jsx";
import get3 from "lodash/get";
import has from "lodash/has";
import isObject3 from "lodash/isObject";
import set2 from "lodash/set";
import unset from "lodash/unset";
import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
var ObjectField = class extends Component3 {
/** Set up the initial state */
state = {
wasPropertyKeyModified: false,
additionalProperties: {}
};
/** Returns a flag indicating whether the `name` field is required in the object schema
*
* @param name - The name of the field to check for required-ness
* @returns - True if the field `name` is required, false otherwise
*/
isRequired(name) {
const { schema } = this.props;
return Array.isArray(schema.required) && schema.required.indexOf(name) !== -1;
}
/** Returns the `onPropertyChange` handler for the `name` field. Handles the special case where a user is attempting
* to clear the data for a field added as an additional property. Calls the `onChange()` handler with the updated
* formData.
*
* @param name - The name of the property
* @param addedByAdditionalProperties - Flag indicating whether this property is an additional property
* @returns - The onPropertyChange callback for the `name` property
*/
onPropertyChange = (name, addedByAdditionalProperties = false) => {
return (value, newErrorSchema, id) => {
const { formData, onChange, errorSchema } = this.props;
if (value === void 0 && addedByAdditionalProperties) {
value = "";
}
const newFormData = { ...formData, [name]: value };
onChange(
newFormData,
errorSchema && errorSchema && {
...errorSchema,
[name]: newErrorSchema
},
id
);
};
};
/** Returns a callback to handle the onDropPropertyClick event for the given `key` which removes the old `key` data
* and calls the `onChange` callback with it
*
* @param key - The key for which the drop callback is desired
* @returns - The drop property click callback
*/
onDropPropertyClick = (key) => {
return (event) => {
event.preventDefault();
const { onChange, formData } = this.props;
const copiedFormData = { ...formData };
unset(copiedFormData, key);
onChange(copiedFormData);
};
};
/** Computes the next available key name from the `preferredKey`, indexing through the already existing keys until one
* that is already not assigned is found.
*
* @param preferredKey - The preferred name of a new key
* @param [formData] - The form data in which to check if the desired key already exists
* @returns - The name of the next available key from `preferredKey`
*/
getAvailableKey = (preferredKey, formData) => {
const { uiSchema, registry } = this.props;
const { duplicateKeySuffixSeparator = "-" } = getUiOptions4(uiSchema, registry.globalUiOptions);
let index = 0;
let newKey = preferredKey;
while (has(formData, newKey)) {
newKey = `${preferredKey}${duplicateKeySuffixSeparator}${++index}`;
}
return newKey;
};
/** Returns a callback function that deals with the rename of a key for an additional property for a schema. That
* callback will attempt to rename the key and move the existing data to that key, calling `onChange` when it does.
*
* @param oldValue - The old value of a field
* @returns - The key change callback function
*/
onKeyChange = (oldValue) => {
return (value, newErrorSchema) => {
if (oldValue === value) {
return;
}
const { formData, onChange, errorSchema } = this.props;
value = this.getAvailableKey(value, formData);
const newFormData = {
...formData
};
const newKeys = { [oldValue]: value };
const keyValues = Object.keys(newFormData).map((key) => {
const newKey = newKeys[key] || key;
return { [newKey]: newFormData[key] };
});
const renamedObj = Object.assign({}, ...keyValues);
this.setState({ wasPropertyKeyModified: true });
onChange(
renamedObj,
errorSchema && errorSchema && {
...errorSchema,
[value]: newErrorSchema
}
);
};
};
/** Returns a default value to be used for a new additional schema property of the given `type`
*
* @param type - The type of the new additional schema property
*/
getDefaultValue(type) {
const {
registry: { translateString }
} = this.props;
switch (type) {
case "array":
return [];
case "boolean":
return false;
case "null":
return null;
case "number":
return 0;
case "object":
return {};
case "string":
default:
return translateString(TranslatableString4.NewStringDefault);
}
}
/** Handles the adding of a new additional property on the given `schema`. Calls the `onChange` callback once the new
* default data for that field has been added to the formData.
*
* @param schema - The schema element to which the new property is being added
*/
handleAddClick = (schema) => () => {
if (!schema.additionalProperties) {
return;
}
const { formData, onChange, registry } = this.props;
const newFormData = { ...formData };
let type = void 0;
let constValue = void 0;
let defaultValue = void 0;
if (isObject3(schema.additionalProperties)) {
type = schema.additionalProperties.type;
constValue = schema.additionalProperties.const;
defaultValue = schema.additionalProperties.default;
let apSchema = schema.additionalProperties;
if (REF_KEY in apSchema) {
const { schemaUtils } = registry;
apSchema = schemaUtils.retrieveSchema({ $ref: apSchema[REF_KEY] }, formData);
type = apSchema.type;
constValue = apSchema.const;
defaultValue = apSchema.default;
}
if (!type && (ANY_OF_KEY2 in apSchema || ONE_OF_KEY2 in apSchema)) {
type = "object";
}
}
const newKey = this.getAvailableKey("newKey", newFormData);
const newValue = constValue ?? defaultValue ?? this.getDefaultValue(type);
set2(newFormData, newKey, newValue);
onChange(newFormData);
};
/** Renders the `ObjectField` from the given props
*/
render() {
const {
schema: rawSchema,
uiSchema = {},
formData,
errorSchema,
idSchema,
name,
required = false,
disabled,
readonly,
hideError,
idPrefix,
idSeparator,
onBlur,
onFocus,
registry,
title
} = this.props;
const { fields: fields2, formContext, schemaUtils, translateString, globalUiOptions } = registry;
const { SchemaField: SchemaField2 } = fields2;
const schema = schemaUtils.retrieveSchema(rawSchema, formData);
const uiOptions = getUiOptions4(uiSchema, globalUiOptions);
const { properties: schemaProperties = {} } = schema;
const templateTitle = uiOptions.title ?? schema.title ?? title ?? name;
const description = uiOptions.description ?? schema.description;
let orderedProperties;
try {
const properties = Object.keys(schemaProperties);
orderedProperties = orderProperties(properties, uiOptions.order);
} catch (err) {
return /* @__PURE__ */ jsxs2("div", { children: [
/* @__PURE__ */ jsx5("p", { className: "config-error", style: { color: "red" }, children: /* @__PURE__ */ jsx5(Markdown, { options: { disableParsingRawHTML: true }, children: translateString(TranslatableString4.InvalidObjectField, [name || "root", err.message]) }) }),
/* @__PURE__ */ jsx5("pre", { children: JSON.stringify(schema) })
] });
}
const Template = getTemplate2("ObjectFieldTemplate", registry, uiOptions);
const templateProps = {
// getDisplayLabel() always returns false for object types, so just check the `uiOptions.label`
title: uiOptions.label === false ? "" : templateTitle,
description: uiOptions.label === false ? void 0 : description,
properties: orderedProperties.map((name2) => {
const addedByAdditionalProperties = has(schema, [PROPERTIES_KEY, name2, ADDITIONAL_PROPERTY_FLAG]);
const fieldUiSchema = addedByAdditionalProperties ? uiSchema.additionalProperties : uiSchema[name2];
const hidden = getUiOptions4(fieldUiSchema).widget === "hidden";
const fieldIdSchema = get3(idSchema, [name2], {});
return {
content: /* @__PURE__ */ jsx5(
SchemaField2,
{
name: name2,
required: this.isRequired(name2),
schema: get3(schema, [PROPERTIES_KEY, name2], {}),
uiSchema: fieldUiSchema,
errorSchema: get3(errorSchema, name2),
idSchema: fieldIdSchema,
idPrefix,
idSeparator,
formData: get3(formData, name2),
formContext,
wasPropertyKeyModified: this.state.wasPropertyKeyModified,
onKeyChange: this.onKeyChange(name2),
onChange: this.onPropertyChange(name2, addedByAdditionalProperties),
onBlur,
onFocus,
registry,
disabled,
readonly,
hideError,
onDropPropertyClick: this.onDropPropertyClick
},
name2
),
name: name2,
readonly,
disabled,
required,
hidden
};
}),
readonly,
disabled,
required,
idSchema,
uiSchema,
errorSchema,
schema,
formData,
formContext,
registry
};
return /* @__PURE__ */ jsx5(Template, { ...templateProps, onAddClick: this.handleAddClick });
}
};
var ObjectField_default = ObjectField;
// src/components/fields/SchemaField.tsx
import { useCallback as useCallback2, Component as Component4 } from "react";
import {
ADDITIONAL_PROPERTY_FLAG as ADDITIONAL_PROPERTY_FLAG2,
deepEquals as deepEquals2,
descriptionId,
getSchemaType,
getTemplate as getTemplate3,
getUiOptions as getUiOptions5,
ID_KEY,
mergeObjects,
TranslatableString as TranslatableString5,
UI_OPTIONS_KEY
} from "@rjsf/utils";
import isObject4 from "lodash/isObject";
import omit2 from "lodash/omit";
import Markdown2 from "markdown-to-jsx";
import { Fragment, jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
var COMPONENT_TYPES = {
array: "ArrayField",
boolean: "BooleanField",
integer: "NumberField",
number: "NumberField",
object: "ObjectField",
string: "StringField",
null: "NullField"
};
function getFieldComponent(schema, uiOptions, idSchema, registry) {
const field = uiOptions.field;
const { fields: fields2, translateString } = registry;
if (typeof field === "function") {
return field;
}
if (typeof field === "string" && field in fields2) {
return fields2[field];
}
const schemaType = getSchemaType(schema);
const type = Array.isArray(schemaType) ? schemaType[0] : schemaType || "";
const schemaId = schema.$id;
let componentName = COMPONENT_TYPES[type];
if (schemaId && schemaId in fields2) {
componentName = schemaId;
}
if (!componentName && (schema.anyOf || schema.oneOf)) {
return () => null;
}
return componentName in fields2 ? fields2[componentName] : () => {
const UnsupportedFieldTemplate = getTemplate3(
"UnsupportedFieldTemplate",
registry,
uiOptions
);
return /* @__PURE__ */ jsx6(
UnsupportedFieldTemplate,
{
schema,
idSchema,
reason: translateS