UNPKG

@rjsf/core

Version:

A simple React component capable of building HTML forms out of a JSON schema.

4 lines 397 kB
{ "version": 3, "sources": ["../src/index.ts", "../src/components/Form.tsx", "../src/getDefaultRegistry.ts", "../src/components/fields/ArrayField.tsx", "../src/components/fields/BooleanField.tsx", "../src/components/fields/FallbackField.tsx", "../src/components/fields/LayoutGridField.tsx", "../src/components/fields/LayoutHeaderField.tsx", "../src/components/fields/LayoutMultiSchemaField.tsx", "../src/components/fields/MultiSchemaField.tsx", "../src/components/fields/NumberField.tsx", "../src/components/fields/ObjectField.tsx", "../src/components/constants.ts", "../src/components/fields/OptionalDataControlsField.tsx", "../src/components/fields/SchemaField.tsx", "../src/components/fields/StringField.tsx", "../src/components/fields/NullField.tsx", "../src/components/fields/index.ts", "../src/components/templates/ArrayFieldDescriptionTemplate.tsx", "../src/components/templates/ArrayFieldItemTemplate.tsx", "../src/components/templates/ArrayFieldItemButtonsTemplate.tsx", "../src/components/templates/ArrayFieldTemplate.tsx", "../src/components/templates/ArrayFieldTitleTemplate.tsx", "../src/components/templates/BaseInputTemplate.tsx", "../src/components/SchemaExamples.tsx", "../src/components/templates/ButtonTemplates/SubmitButton.tsx", "../src/components/templates/ButtonTemplates/AddButton.tsx", "../src/components/templates/ButtonTemplates/IconButton.tsx", "../src/components/templates/ButtonTemplates/index.ts", "../src/components/RichDescription.tsx", "../src/components/templates/DescriptionField.tsx", "../src/components/templates/ErrorList.tsx", "../src/components/templates/FallbackFieldTemplate.tsx", "../src/components/templates/FieldTemplate/FieldTemplate.tsx", "../src/components/templates/FieldTemplate/Label.tsx", "../src/components/templates/FieldTemplate/index.ts", "../src/components/templates/FieldErrorTemplate.tsx", "../src/components/templates/FieldHelpTemplate.tsx", "../src/components/RichHelp.tsx", "../src/components/templates/GridTemplate.tsx", "../src/components/templates/MultiSchemaFieldTemplate.tsx", "../src/components/templates/ObjectFieldTemplate.tsx", "../src/components/templates/OptionalDataControlsTemplate.tsx", "../src/components/templates/TitleField.tsx", "../src/components/templates/UnsupportedField.tsx", "../src/components/templates/WrapIfAdditionalTemplate.tsx", "../src/components/templates/index.ts", "../src/components/widgets/AltDateWidget.tsx", "../src/components/widgets/AltDateTimeWidget.tsx", "../src/components/widgets/CheckboxWidget.tsx", "../src/components/widgets/CheckboxesWidget.tsx", "../src/components/widgets/ColorWidget.tsx", "../src/components/widgets/DateWidget.tsx", "../src/components/widgets/DateTimeWidget.tsx", "../src/components/widgets/EmailWidget.tsx", "../src/components/widgets/FileWidget.tsx", "../src/components/widgets/HiddenWidget.tsx", "../src/components/widgets/PasswordWidget.tsx", "../src/components/widgets/RadioWidget.tsx", "../src/components/widgets/RangeWidget.tsx", "../src/components/widgets/RatingWidget.tsx", "../src/components/widgets/SelectWidget.tsx", "../src/components/widgets/TextareaWidget.tsx", "../src/components/widgets/TextWidget.tsx", "../src/components/widgets/TimeWidget.tsx", "../src/components/widgets/URLWidget.tsx", "../src/components/widgets/UpDownWidget.tsx", "../src/components/widgets/index.ts", "../src/withTheme.tsx", "../src/getTestRegistry.tsx"], "sourcesContent": ["import Form, { FormProps, FormState, IChangeEvent } from './components/Form';\nimport RichDescription, { RichDescriptionProps } from './components/RichDescription';\nimport RichHelp, { RichHelpProps } from './components/RichHelp';\nimport SchemaExamples, { SchemaExamplesProps } from './components/SchemaExamples';\nimport withTheme, { ThemeProps } from './withTheme';\nimport getDefaultRegistry from './getDefaultRegistry';\nimport getTestRegistry from './getTestRegistry';\n\nexport type {\n FormProps,\n FormState,\n IChangeEvent,\n ThemeProps,\n RichDescriptionProps,\n RichHelpProps,\n SchemaExamplesProps,\n};\n\nexport { withTheme, getDefaultRegistry, getTestRegistry, RichDescription, RichHelp, SchemaExamples };\nexport default Form;\n", "import { Component, ElementType, FormEvent, ReactNode, Ref, RefObject, createRef } from 'react';\nimport {\n createSchemaUtils,\n CustomValidator,\n deepEquals,\n ErrorSchema,\n ErrorSchemaBuilder,\n ErrorTransformer,\n expandUiSchemaDefinitions,\n FieldPathId,\n FieldPathList,\n FormContextType,\n getChangedFields,\n getTemplate,\n getUiOptions,\n isObject,\n mergeObjects,\n PathSchema,\n StrictRJSFSchema,\n Registry,\n RegistryFieldsType,\n RegistryWidgetsType,\n RJSFSchema,\n RJSFValidationError,\n SchemaUtilsType,\n shouldRender,\n SUBMIT_BTN_OPTIONS_KEY,\n TemplatesType,\n toErrorList,\n toFieldPathId,\n UiSchema,\n UI_DEFINITIONS_KEY,\n UI_GLOBAL_OPTIONS_KEY,\n UI_OPTIONS_KEY,\n ValidationData,\n validationDataMerge,\n ValidatorType,\n Experimental_DefaultFormStateBehavior,\n Experimental_CustomMergeAllOf,\n DEFAULT_ID_SEPARATOR,\n DEFAULT_ID_PREFIX,\n GlobalFormOptions,\n ERRORS_KEY,\n ID_KEY,\n NameGeneratorFunction,\n getUsedFormData,\n getFieldNames,\n} from '@rjsf/utils';\nimport _cloneDeep from 'lodash/cloneDeep';\nimport _get from 'lodash/get';\nimport _isEmpty from 'lodash/isEmpty';\nimport _pick from 'lodash/pick';\nimport _set from 'lodash/set';\nimport _toPath from 'lodash/toPath';\nimport _unset from 'lodash/unset';\n\nimport getDefaultRegistry from '../getDefaultRegistry';\nimport { ADDITIONAL_PROPERTY_KEY_REMOVE, IS_RESET } from './constants';\n\n/** Represents a boolean option that is deprecated.\n * @deprecated - In a future major release, this type will be removed\n */\ntype DeprecatedBooleanOption = boolean;\n\n/** The properties that are passed to the `Form` */\nexport interface FormProps<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any> {\n /** The JSON schema object for the form */\n schema: S;\n /** An implementation of the `ValidatorType` interface that is needed for form validation to work */\n validator: ValidatorType<T, S, F>;\n /** The optional children for the form, if provided, it will replace the default `SubmitButton` */\n children?: ReactNode;\n /** The uiSchema for the form */\n uiSchema?: UiSchema<T, S, F>;\n /** The data for the form, used to load a \"controlled\" form with its current data. If you want an \"uncontrolled\" form\n * with initial data, then use `initialFormData` instead.\n */\n formData?: T;\n /** The initial data for the form, used to fill an \"uncontrolled\" form with existing data on the initial render and\n * when `reset()` is called programmatically.\n */\n initialFormData?: T;\n // Form presentation and behavior modifiers\n /** You can provide a `formContext` object to the form, which is passed down to all fields and widgets. Useful for\n * implementing context aware fields and widgets.\n *\n * NOTE: Setting `{readonlyAsDisabled: false}` on the formContext will make the antd theme treat readOnly fields as\n * disabled.\n */\n formContext?: F;\n /** To avoid collisions with existing ids in the DOM, it is possible to change the prefix used for ids;\n * Default is `root`\n */\n idPrefix?: string;\n /** To avoid using a path separator that is present in field names, it is possible to change the separator used for\n * ids (Default is `_`)\n */\n idSeparator?: string;\n /** It's possible to disable the whole form by setting the `disabled` prop. The `disabled` prop is then forwarded down\n * to each field of the form. If you just want to disable some fields, see the `ui:disabled` parameter in `uiSchema`\n */\n disabled?: boolean;\n /** It's possible to make the whole form read-only by setting the `readonly` prop. The `readonly` prop is then\n * forwarded down to each field of the form. If you just want to make some fields read-only, see the `ui:readonly`\n * parameter in `uiSchema`\n */\n readonly?: boolean;\n // Form registry\n /** The dictionary of registered fields in the form */\n fields?: RegistryFieldsType<T, S, F>;\n /** The dictionary of registered templates in the form; Partial allows a subset to be provided beyond the defaults */\n templates?: Partial<Omit<TemplatesType<T, S, F>, 'ButtonTemplates'>> & {\n ButtonTemplates?: Partial<TemplatesType<T, S, F>['ButtonTemplates']>;\n };\n /** The dictionary of registered widgets in the form */\n widgets?: RegistryWidgetsType<T, S, F>;\n // Callbacks\n /** If you plan on being notified every time the form data are updated, you can pass an `onChange` handler, which will\n * receive the same args as `onSubmit` any time a value is updated in the form. Can also return the `id` of the field\n * that caused the change\n */\n onChange?: (data: IChangeEvent<T, S, F>, id?: string) => void;\n /** To react when submitted form data are invalid, pass an `onError` handler. It will be passed the list of\n * encountered errors\n */\n onError?: (errors: RJSFValidationError[]) => void;\n /** You can pass a function as the `onSubmit` prop of your `Form` component to listen to when the form is submitted\n * and its data are valid. It will be passed a result object having a `formData` attribute, which is the valid form\n * data you're usually after. The original event will also be passed as a second parameter\n */\n onSubmit?: (data: IChangeEvent<T, S, F>, event: FormEvent<any>) => void;\n /** Sometimes you may want to trigger events or modify external state when a field has been touched, so you can pass\n * an `onBlur` handler, which will receive the id of the input that was blurred and the field value\n */\n onBlur?: (id: string, data: any) => void;\n /** Sometimes you may want to trigger events or modify external state when a field has been focused, so you can pass\n * an `onFocus` handler, which will receive the id of the input that is focused and the field value\n */\n onFocus?: (id: string, data: any) => void;\n /** The value of this prop will be passed to the `accept-charset` HTML attribute on the form */\n acceptCharset?: string;\n /** The value of this prop will be passed to the `action` HTML attribute on the form\n *\n * NOTE: this just renders the `action` attribute in the HTML markup. There is no real network request being sent to\n * this `action` on submit. Instead, react-jsonschema-form catches the submit event with `event.preventDefault()`\n * and then calls the `onSubmit` function, where you could send a request programmatically with `fetch` or similar.\n */\n action?: string;\n /** The value of this prop will be passed to the `autocomplete` HTML attribute on the form */\n autoComplete?: string;\n /** The value of this prop will be passed to the `class` HTML attribute on the form */\n className?: string;\n /** The value of this prop will be passed to the `enctype` HTML attribute on the form */\n enctype?: string;\n /** The value of this prop will be passed to the `id` HTML attribute on the form */\n id?: string;\n /** The value of this prop will be passed to the `name` HTML attribute on the form */\n name?: string;\n /** The value of this prop will be passed to the `method` HTML attribute on the form */\n method?: string;\n /** It's possible to change the default `form` tag name to a different HTML tag, which can be helpful if you are\n * nesting forms. However, native browser form behaviour, such as submitting when the `Enter` key is pressed, may no\n * longer work\n */\n tagName?: ElementType;\n /** The value of this prop will be passed to the `target` HTML attribute on the form */\n target?: string;\n // Errors and validation\n /** Formerly the `validate` prop; Takes a function that specifies custom validation rules for the form */\n customValidate?: CustomValidator<T, S, F>;\n /** This prop allows passing in custom errors that are augmented with the existing JSON Schema errors on the form; it\n * can be used to implement asynchronous validation. By default, these are non-blocking errors, meaning that you can\n * still submit the form when these are the only errors displayed to the user.\n */\n extraErrors?: ErrorSchema<T>;\n /** If set to true, causes the `extraErrors` to become blocking when the form is submitted */\n extraErrorsBlockSubmit?: boolean;\n /** If set to true, turns off HTML5 validation on the form; Set to `false` by default */\n noHtml5Validate?: boolean;\n /** If set to true, turns off all validation. Set to `false` by default\n *\n * @deprecated - In a future release, this switch may be replaced by making `validator` prop optional\n */\n noValidate?: boolean;\n /** Flag that describes when live validation will be performed. Live validation means that the form will perform\n * validation and show any validation errors whenever the form data is updated, rather than just on submit.\n *\n * If no value (or `false`) is provided, then live validation will not happen. If `true` or `onChange` is provided for\n * the flag, then live validation will be performed after processing of all pending changes has completed. If `onBlur`\n * is provided, then live validation will be performed when a field that was updated is blurred (as a performance\n * optimization).\n *\n * NOTE: In a future major release, the `boolean` options for this flag will be removed\n */\n liveValidate?: 'onChange' | 'onBlur' | DeprecatedBooleanOption;\n /** Flag that describes when live omit will be performed. Live omit happens only when `omitExtraData` is also set to\n * to `true` and the form's data is updated by the user.\n *\n * If no value (or `false`) is provided, then live omit will not happen. If `true` or `onChange` is provided for\n * the flag, then live omit will be performed after processing of all pending changes has completed. If `onBlur`\n * is provided, then live omit will be performed when a field that was updated is blurred (as a performance\n * optimization).\n *\n * NOTE: In a future major release, the `boolean` options for this flag will be removed\n */\n liveOmit?: 'onChange' | 'onBlur' | DeprecatedBooleanOption;\n /** If set to true, then extra form data values that are not in any form field will be removed whenever `onSubmit` is\n * called. Set to `false` by default.\n */\n omitExtraData?: boolean;\n /** When this prop is set to `top` or 'bottom', a list of errors (or the custom error list defined in the `ErrorList`) will also\n * show. When set to false, only inline input validation errors will be shown. Set to `top` by default\n */\n showErrorList?: false | 'top' | 'bottom';\n /** A function can be passed to this prop in order to make modifications to the default errors resulting from JSON\n * Schema validation\n */\n transformErrors?: ErrorTransformer<T, S, F>;\n /** If set to true, then the first field with an error will receive the focus when the form is submitted with errors\n */\n focusOnFirstError?: boolean | ((error: RJSFValidationError) => void);\n /** Optional string translation function, if provided, allows users to change the translation of the RJSF internal\n * strings. Some strings contain replaceable parameter values as indicated by `%1`, `%2`, etc. The number after the\n * `%` indicates the order of the parameter. The ordering of parameters is important because some languages may choose\n * to put the second parameter before the first in its translation.\n */\n translateString?: Registry['translateString'];\n /** Optional function to generate custom HTML `name` attributes for form fields.\n */\n nameGenerator?: NameGeneratorFunction;\n /** Optional flag that, when set to true, will cause the `FallbackField` to render a type selector for unsupported\n * fields instead of the default UnsupportedField error UI.\n */\n useFallbackUiForUnsupportedType?: boolean;\n /** Optional configuration object with flags, if provided, allows users to override default form state behavior\n * Currently only affecting minItems on array fields and handling of setting defaults based on the value of\n * `emptyObjectFields`\n */\n experimental_defaultFormStateBehavior?: Experimental_DefaultFormStateBehavior;\n /**\n * Controls the component update strategy used by the Form's `shouldComponentUpdate` lifecycle method.\n *\n * - `'customDeep'`: Uses RJSF's custom deep equality checks via the `deepEquals` utility function,\n * which treats all functions as equivalent and provides optimized performance for form data comparisons.\n * - `'shallow'`: Uses shallow comparison of props and state (only compares direct properties). This matches React's PureComponent behavior.\n * - `'always'`: Always rerenders when called. This matches React's Component behavior.\n *\n * @default 'customDeep'\n */\n experimental_componentUpdateStrategy?: 'customDeep' | 'shallow' | 'always';\n /** Optional function that allows for custom merging of `allOf` schemas\n */\n experimental_customMergeAllOf?: Experimental_CustomMergeAllOf<S>;\n // Private\n /**\n * _internalFormWrapper is currently used by the semantic-ui theme to provide a custom wrapper around `<Form />`\n * that supports the proper rendering of those themes. To use this prop, one must pass a component that takes two\n * props: `children` and `as`. That component, at minimum, should render the `children` inside of a <form /> tag\n * unless `as` is provided, in which case, use the `as` prop in place of `<form />`.\n * i.e.:\n * ```\n * export default function InternalForm({ children, as }) {\n * const FormTag = as || 'form';\n * return <FormTag>{children}</FormTag>;\n * }\n * ```\n *\n * Use at your own risk as this prop is private and may change at any time without notice.\n */\n _internalFormWrapper?: ElementType;\n /** Support receiving a React ref to the Form\n */\n ref?: Ref<Form<T, S, F>>;\n}\n\n/** The data that is contained within the state for the `Form` */\nexport interface FormState<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any> {\n /** The JSON schema object for the form */\n schema: S;\n /** The uiSchema for the form */\n uiSchema: UiSchema<T, S, F>;\n /** The `FieldPathId` for the form, computed from the `schema`, the `rootFieldId`, the `idPrefix` and\n * `idSeparator` props.\n */\n fieldPathId: FieldPathId;\n /** The schemaUtils implementation used by the `Form`, created from the `validator` and the `schema` */\n schemaUtils: SchemaUtilsType<T, S, F>;\n /** The current data for the form, computed from the `formData` prop and the changes made by the user */\n formData?: T;\n /** Flag indicating whether the form is in edit mode, true when `formData` is passed to the form, otherwise false */\n edit: boolean;\n /** The current list of errors for the form, includes `extraErrors` */\n errors: RJSFValidationError[];\n /** The current errors, in `ErrorSchema` format, for the form, includes `extraErrors` */\n errorSchema: ErrorSchema<T>;\n // Private\n /** The current list of errors for the form directly from schema validation, does NOT include `extraErrors` */\n schemaValidationErrors: RJSFValidationError[];\n /** The current errors, in `ErrorSchema` format, for the form directly from schema validation, does NOT include\n * `extraErrors`\n */\n schemaValidationErrorSchema: ErrorSchema<T>;\n /** A container used to handle custom errors provided via `onChange` */\n customErrors?: ErrorSchemaBuilder<T>;\n /** @description result of schemaUtils.retrieveSchema(schema, formData). This a memoized value to avoid re calculate at internal functions (getStateFromProps, onChange) */\n retrievedSchema: S;\n /** Flag indicating whether the initial form defaults have been generated */\n initialDefaultsGenerated: boolean;\n /** The registry (re)computed only when props changed */\n registry: Registry<T, S, F>;\n}\n\n/** The event data passed when changes have been made to the form, includes everything from the `FormState` except\n * the schema validation errors. An additional `status` is added when returned from `onSubmit`\n */\nexport interface IChangeEvent<\n T = any,\n S extends StrictRJSFSchema = RJSFSchema,\n F extends FormContextType = any,\n> extends Pick<\n FormState<T, S, F>,\n 'schema' | 'uiSchema' | 'fieldPathId' | 'schemaUtils' | 'formData' | 'edit' | 'errors' | 'errorSchema'\n> {\n /** The status of the form when submitted */\n status?: 'submitted';\n}\n\n/** Converts the full `FormState` into the `IChangeEvent` version by picking out the public values\n *\n * @param state - The state of the form\n * @param status - The status provided by the onSubmit\n * @returns - The `IChangeEvent` for the state\n */\nfunction toIChangeEvent<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any>(\n state: FormState<T, S, F>,\n status?: IChangeEvent['status'],\n): IChangeEvent<T, S, F> {\n return {\n ..._pick(state, ['schema', 'uiSchema', 'fieldPathId', 'schemaUtils', 'formData', 'edit', 'errors', 'errorSchema']),\n ...(status !== undefined && { status }),\n };\n}\n\n/** The definition of a pending change that will be processed in the `onChange` handler\n */\ninterface PendingChange<T> {\n /** The path into the formData/errorSchema at which the `newValue`/`newErrorSchema` will be set */\n path: FieldPathList;\n /** The new value to set into the formData */\n newValue?: T;\n /** The new errors to be set into the errorSchema, if any */\n newErrorSchema?: ErrorSchema<T>;\n /** The optional id of the field for which the change is being made */\n id?: string;\n}\n\n/** The `Form` component renders the outer form and all the fields defined in the `schema` */\nexport default class Form<\n T = any,\n S extends StrictRJSFSchema = RJSFSchema,\n F extends FormContextType = any,\n> extends Component<FormProps<T, S, F>, FormState<T, S, F>> {\n /** The ref used to hold the `form` element, this needs to be `any` because `tagName` or `_internalFormWrapper` can\n * provide any possible type here\n */\n formElement: RefObject<any>;\n\n /** The list of pending changes\n */\n pendingChanges: PendingChange<T>[] = [];\n\n /** Flag to track when we're processing a user-initiated field change.\n * This prevents componentDidUpdate from reverting oneOf/anyOf option switches.\n */\n private _isProcessingUserChange = false;\n\n /** Constructs the `Form` from the `props`. Will setup the initial state from the props. It will also call the\n * `onChange` handler if the initially provided `formData` is modified to add missing default values as part of the\n * state construction.\n *\n * @param props - The initial props for the `Form`\n */\n constructor(props: FormProps<T, S, F>) {\n super(props);\n\n if (!props.validator) {\n throw new Error('A validator is required for Form functionality to work');\n }\n\n const { formData: propsFormData, initialFormData, onChange } = props;\n const formData = propsFormData ?? initialFormData;\n this.state = this.getStateFromProps(props, formData, undefined, undefined, undefined, true);\n if (onChange && !deepEquals(this.state.formData, formData)) {\n onChange(toIChangeEvent(this.state));\n }\n this.formElement = createRef();\n }\n\n /**\n * `getSnapshotBeforeUpdate` is a React lifecycle method that is invoked right before the most recently rendered\n * output is committed to the DOM. It enables your component to capture current values (e.g., scroll position) before\n * they are potentially changed.\n *\n * In this case, it checks if the props have changed since the last render. If they have, it computes the next state\n * of the component using `getStateFromProps` method and returns it along with a `shouldUpdate` flag set to `true` IF\n * the `nextState` and `prevState` are different, otherwise `false`. This ensures that we have the most up-to-date\n * state ready to be applied in `componentDidUpdate`.\n *\n * If `formData` hasn't changed, it simply returns an object with `shouldUpdate` set to `false`, indicating that a\n * state update is not necessary.\n *\n * @param prevProps - The previous set of props before the update.\n * @param prevState - The previous state before the update.\n * @returns Either an object containing the next state and a flag indicating that an update should occur, or an object\n * with a flag indicating that an update is not necessary.\n */\n getSnapshotBeforeUpdate(\n prevProps: FormProps<T, S, F>,\n prevState: FormState<T, S, F>,\n ): { nextState: FormState<T, S, F>; shouldUpdate: true } | { shouldUpdate: false } {\n if (!deepEquals(this.props, prevProps)) {\n // Compare the previous props formData against the current props formData\n const formDataChangedFields = getChangedFields(this.props.formData, prevProps.formData);\n // Compare the current props formData against the current state's formData to determine if the new props were the\n // result of the onChange from the existing state formData\n const stateDataChangedFields = getChangedFields(this.props.formData, this.state.formData);\n const isSchemaChanged = !deepEquals(prevProps.schema, this.props.schema);\n // When formData is not an object, getChangedFields returns an empty array.\n // In this case, deepEquals is most needed to check again.\n const isFormDataChanged =\n formDataChangedFields.length > 0 || !deepEquals(prevProps.formData, this.props.formData);\n const isStateDataChanged =\n stateDataChangedFields.length > 0 || !deepEquals(this.state.formData, this.props.formData);\n const nextState = this.getStateFromProps(\n this.props,\n this.props.formData,\n // If the `schema` has changed, we need to update the retrieved schema.\n // Or if the `formData` changes, for example in the case of a schema with dependencies that need to\n // match one of the subSchemas, the retrieved schema must be updated.\n isSchemaChanged || isFormDataChanged ? undefined : this.state.retrievedSchema,\n isSchemaChanged,\n formDataChangedFields,\n // Skip live validation for this request if no form data has changed from the last state\n !isStateDataChanged,\n );\n const shouldUpdate = !deepEquals(nextState, prevState);\n return { nextState, shouldUpdate };\n }\n return { shouldUpdate: false };\n }\n\n /**\n * `componentDidUpdate` is a React lifecycle method that is invoked immediately after updating occurs. This method is\n * not called for the initial render.\n *\n * Here, it checks if an update is necessary based on the `shouldUpdate` flag received from `getSnapshotBeforeUpdate`.\n * If an update is required, it applies the next state and, if needed, triggers the `onChange` handler to inform about\n * changes.\n *\n * @param _ - The previous set of props.\n * @param prevState - The previous state of the component before the update.\n * @param snapshot - The value returned from `getSnapshotBeforeUpdate`.\n */\n componentDidUpdate(\n _: FormProps<T, S, F>,\n prevState: FormState<T, S, F>,\n snapshot: { nextState: FormState<T, S, F>; shouldUpdate: true } | { shouldUpdate: false },\n ) {\n if (snapshot.shouldUpdate) {\n const { nextState } = snapshot;\n\n // Prevent oneOf/anyOf option switches from reverting when getStateFromProps\n // re-evaluates and produces stale formData.\n const nextStateDiffersFromProps = !deepEquals(nextState.formData, this.props.formData);\n const wasProcessingUserChange = this._isProcessingUserChange;\n this._isProcessingUserChange = false;\n\n if (wasProcessingUserChange && nextStateDiffersFromProps) {\n // Skip - the user's option switch is already applied via processPendingChange\n return;\n }\n\n if (nextStateDiffersFromProps && !deepEquals(nextState.formData, prevState.formData) && this.props.onChange) {\n this.props.onChange(toIChangeEvent(nextState));\n }\n this.setState(nextState);\n }\n }\n\n /** Extracts the updated state from the given `props` and `inputFormData`. As part of this process, the\n * `inputFormData` is first processed to add any missing required defaults. After that, the data is run through the\n * validation process IF required by the `props`.\n *\n * @param props - The props passed to the `Form`\n * @param inputFormData - The new or current data for the `Form`\n * @param retrievedSchema - An expanded schema, if not provided, it will be retrieved from the `schema` and `formData`.\n * @param isSchemaChanged - A flag indicating whether the schema has changed.\n * @param formDataChangedFields - The changed fields of `formData`\n * @param skipLiveValidate - Optional flag, if true, means that we are not running live validation\n * @returns - The new state for the `Form`\n */\n getStateFromProps(\n props: FormProps<T, S, F>,\n inputFormData?: T,\n retrievedSchema?: S,\n isSchemaChanged = false,\n formDataChangedFields: string[] = [],\n skipLiveValidate = false,\n ): FormState<T, S, F> {\n const state: FormState<T, S, F> = this.state || {};\n const schema = 'schema' in props ? props.schema : this.props.schema;\n const validator = 'validator' in props ? props.validator : this.props.validator;\n const uiSchema: UiSchema<T, S, F> = ('uiSchema' in props ? props.uiSchema! : this.props.uiSchema!) || {};\n const isUncontrolled = props.formData === undefined && this.props.formData === undefined;\n const edit = typeof inputFormData !== 'undefined';\n const liveValidate = 'liveValidate' in props ? props.liveValidate : this.props.liveValidate;\n const mustValidate = edit && !props.noValidate && liveValidate;\n const experimental_defaultFormStateBehavior =\n 'experimental_defaultFormStateBehavior' in props\n ? props.experimental_defaultFormStateBehavior\n : this.props.experimental_defaultFormStateBehavior;\n const experimental_customMergeAllOf =\n 'experimental_customMergeAllOf' in props\n ? props.experimental_customMergeAllOf\n : this.props.experimental_customMergeAllOf;\n let schemaUtils: SchemaUtilsType<T, S, F> = state.schemaUtils;\n if (\n !schemaUtils ||\n schemaUtils.doesSchemaUtilsDiffer(\n validator,\n schema,\n experimental_defaultFormStateBehavior,\n experimental_customMergeAllOf,\n )\n ) {\n schemaUtils = createSchemaUtils<T, S, F>(\n validator,\n schema,\n experimental_defaultFormStateBehavior,\n experimental_customMergeAllOf,\n );\n }\n\n const rootSchema = schemaUtils.getRootSchema();\n\n // Compute the formData for getDefaultFormState() function based on the inputFormData, isUncontrolled and state\n let defaultsFormData = inputFormData;\n if (inputFormData === IS_RESET) {\n defaultsFormData = undefined;\n } else if (inputFormData === undefined && isUncontrolled) {\n defaultsFormData = state.formData;\n }\n const formData: T = schemaUtils.getDefaultFormState(\n rootSchema,\n defaultsFormData,\n false,\n state.initialDefaultsGenerated,\n ) as T;\n const _retrievedSchema = this.updateRetrievedSchema(\n retrievedSchema ?? schemaUtils.retrieveSchema(rootSchema, formData),\n );\n\n const getCurrentErrors = (): ValidationData<T> => {\n // If the `props.noValidate` option is set or the schema has changed, we reset the error state.\n if (props.noValidate || isSchemaChanged) {\n return { errors: [], errorSchema: {} };\n } else if (!props.liveValidate) {\n return {\n errors: state.schemaValidationErrors || [],\n errorSchema: state.schemaValidationErrorSchema || {},\n };\n }\n return {\n errors: state.errors || [],\n errorSchema: state.errorSchema || {},\n };\n };\n\n let errors: RJSFValidationError[];\n let errorSchema: ErrorSchema<T> | undefined;\n let schemaValidationErrors: RJSFValidationError[] = state.schemaValidationErrors;\n let schemaValidationErrorSchema: ErrorSchema<T> = state.schemaValidationErrorSchema;\n // If we are skipping live validate, it means that the state has already been updated with live validation errors\n if (mustValidate && !skipLiveValidate) {\n const liveValidation = this.liveValidate(\n rootSchema,\n schemaUtils,\n state.errorSchema,\n formData,\n undefined,\n state.customErrors,\n retrievedSchema,\n // If retrievedSchema is undefined which means the schema or formData has changed, we do not merge state.\n // Else in the case where it hasn't changed,\n retrievedSchema !== undefined,\n );\n errors = liveValidation.errors;\n errorSchema = liveValidation.errorSchema;\n schemaValidationErrors = liveValidation.schemaValidationErrors;\n schemaValidationErrorSchema = liveValidation.schemaValidationErrorSchema;\n } else {\n const currentErrors = getCurrentErrors();\n errors = currentErrors.errors;\n errorSchema = currentErrors.errorSchema;\n // We only update the error schema for changed fields if mustValidate is false\n if (formDataChangedFields.length > 0 && !mustValidate) {\n const newErrorSchema = formDataChangedFields.reduce(\n (acc, key) => {\n acc[key] = undefined;\n return acc;\n },\n {} as Record<string, undefined>,\n );\n errorSchema = schemaValidationErrorSchema = mergeObjects(\n currentErrors.errorSchema,\n newErrorSchema,\n 'preventDuplicates',\n ) as ErrorSchema<T>;\n }\n const mergedErrors = this.mergeErrors({ errorSchema, errors }, props.extraErrors, state.customErrors);\n errors = mergedErrors.errors;\n errorSchema = mergedErrors.errorSchema;\n }\n\n // Only store a new registry when the props cause a different one to be created\n const newRegistry = this.getRegistry(props, rootSchema, schemaUtils);\n const registry = deepEquals(state.registry, newRegistry) ? state.registry : newRegistry;\n\n // Pre-expand ui:definitions into the uiSchema structure (must happen after registry is created)\n const expandedUiSchema: UiSchema<T, S, F> = registry.uiSchemaDefinitions\n ? expandUiSchemaDefinitions<T, S, F>(rootSchema, uiSchema, registry)\n : uiSchema;\n\n // Only compute a new `fieldPathId` when the `idPrefix` is different than the existing fieldPathId's ID_KEY\n const fieldPathId =\n state.fieldPathId && state.fieldPathId?.[ID_KEY] === registry.globalFormOptions.idPrefix\n ? state.fieldPathId\n : toFieldPathId('', registry.globalFormOptions);\n const nextState: FormState<T, S, F> = {\n schemaUtils,\n schema: rootSchema,\n uiSchema: expandedUiSchema,\n fieldPathId,\n formData,\n edit,\n errors,\n errorSchema,\n schemaValidationErrors,\n schemaValidationErrorSchema,\n retrievedSchema: _retrievedSchema,\n initialDefaultsGenerated: true,\n registry,\n };\n return nextState;\n }\n\n /** React lifecycle method that is used to determine whether component should be updated.\n *\n * @param nextProps - The next version of the props\n * @param nextState - The next version of the state\n * @returns - True if the component should be updated, false otherwise\n */\n shouldComponentUpdate(nextProps: FormProps<T, S, F>, nextState: FormState<T, S, F>): boolean {\n const { experimental_componentUpdateStrategy = 'customDeep' } = this.props;\n return shouldRender(this, nextProps, nextState, experimental_componentUpdateStrategy);\n }\n\n /** Validates the `formData` against the `schema` using the `altSchemaUtils` (if provided otherwise it uses the\n * `schemaUtils` in the state), returning the results.\n *\n * @param formData - The new form data to validate\n * @param schema - The schema used to validate against\n * @param [altSchemaUtils] - The alternate schemaUtils to use for validation\n * @param [retrievedSchema] - An optionally retrieved schema for per\n */\n validate(\n formData: T | undefined,\n schema = this.state.schema,\n altSchemaUtils?: SchemaUtilsType<T, S, F>,\n retrievedSchema?: S,\n ): ValidationData<T> {\n const schemaUtils = altSchemaUtils ? altSchemaUtils : this.state.schemaUtils;\n const { customValidate, transformErrors, uiSchema } = this.props;\n const resolvedSchema = retrievedSchema ?? schemaUtils.retrieveSchema(schema, formData);\n return schemaUtils\n .getValidator()\n .validateFormData(formData, resolvedSchema, customValidate, transformErrors, uiSchema);\n }\n\n /** Renders any errors contained in the `state` in using the `ErrorList`, if not disabled by `showErrorList`. */\n renderErrors(registry: Registry<T, S, F>) {\n const { errors, errorSchema, schema, uiSchema } = this.state;\n const options = getUiOptions<T, S, F>(uiSchema);\n const ErrorListTemplate = getTemplate<'ErrorListTemplate', T, S, F>('ErrorListTemplate', registry, options);\n\n if (errors && errors.length) {\n return (\n <ErrorListTemplate\n errors={errors}\n errorSchema={errorSchema || {}}\n schema={schema}\n uiSchema={uiSchema}\n registry={registry}\n />\n );\n }\n return null;\n }\n\n /** Merges any `extraErrors` or `customErrors` into the given `schemaValidation` object, returning the result\n *\n * @param schemaValidation - The `ValidationData` object into which additional errors are merged\n * @param [extraErrors] - The extra errors from the props\n * @param [customErrors] - The customErrors from custom components\n * @return - The `extraErrors` and `customErrors` merged into the `schemaValidation`\n * @private\n */\n private mergeErrors(\n schemaValidation: ValidationData<T>,\n extraErrors?: FormProps['extraErrors'],\n customErrors?: ErrorSchemaBuilder,\n ): ValidationData<T> {\n let errorSchema: ErrorSchema<T> = schemaValidation.errorSchema;\n let errors: RJSFValidationError[] = schemaValidation.errors;\n if (extraErrors) {\n const merged = validationDataMerge(schemaValidation, extraErrors);\n errorSchema = merged.errorSchema;\n errors = merged.errors;\n }\n if (customErrors) {\n const merged = validationDataMerge(schemaValidation, customErrors.ErrorSchema, true);\n errorSchema = merged.errorSchema;\n errors = merged.errors;\n }\n return { errors, errorSchema };\n }\n\n /** Performs live validation and then updates and returns the errors and error schemas by potentially merging in\n * `extraErrors` and `customErrors`.\n *\n * @param rootSchema - The `rootSchema` from the state\n * @param schemaUtils - The `SchemaUtilsType` from the state\n * @param originalErrorSchema - The original `ErrorSchema` from the state\n * @param [formData] - The new form data to validate\n * @param [extraErrors] - The extra errors from the props\n * @param [customErrors] - The customErrors from custom components\n * @param [retrievedSchema] - An expanded schema, if not provided, it will be retrieved from the `schema` and `formData`\n * @param [mergeIntoOriginalErrorSchema=false] - Optional flag indicating whether we merge into original schema\n * @returns - An object containing `errorSchema`, `errors`, `schemaValidationErrors` and `schemaValidationErrorSchema`\n * @private\n */\n private liveValidate(\n rootSchema: S,\n schemaUtils: SchemaUtilsType<T, S, F>,\n originalErrorSchema: ErrorSchema<S>,\n formData?: T,\n extraErrors?: FormProps['extraErrors'],\n customErrors?: ErrorSchemaBuilder<T>,\n retrievedSchema?: S,\n mergeIntoOriginalErrorSchema = false,\n ) {\n const schemaValidation = this.validate(formData, rootSchema, schemaUtils, retrievedSchema);\n const errors = schemaValidation.errors;\n let errorSchema = schemaValidation.errorSchema;\n // We merge 'originalErrorSchema' with 'schemaValidation.errorSchema.'; This done to display the raised field error.\n if (mergeIntoOriginalErrorSchema) {\n errorSchema = mergeObjects(\n originalErrorSchema,\n schemaValidation.errorSchema,\n 'preventDuplicates',\n ) as ErrorSchema<T>;\n }\n const schemaValidationErrors = errors;\n const schemaValidationErrorSchema = errorSchema;\n const mergedErrors = this.mergeErrors({ errorSchema, errors }, extraErrors, customErrors);\n return { ...mergedErrors, schemaValidationErrors, schemaValidationErrorSchema };\n }\n\n /** Returns the `formData` with only the elements specified in the `fields` list\n *\n * @param formData - The data for the `Form`\n * @param fields - The fields to keep while filtering\n * @deprecated - To be removed as an exported `Form` function in a future release; there isn't a planned replacement\n */\n getUsedFormData = (formData: T | undefined, fields: string[]): T | undefined => {\n return getUsedFormData(formData, fields);\n };\n\n /** Returns the list of field names from inspecting the `pathSchema` as well as using the `formData`\n *\n * @param pathSchema - The `PathSchema` object for the form\n * @param [formData] - The form data to use while checking for empty objects/arrays\n * @deprecated - To be removed as an exported `Form` function in a future release; there isn't a planned replacement\n */\n getFieldNames = (pathSchema: PathSchema<T>, formData?: T): string[][] => {\n return getFieldNames(pathSchema, formData);\n };\n\n /** Returns the `formData` after filtering to remove any extra data not in a form field\n *\n * @param formData - The data for the `Form`\n * @returns The `formData` after omitting extra data\n * @deprecated - To be removed as an exported `Form` function in a future release, use `SchemaUtils.omitExtraData`\n * instead.\n */\n omitExtraData = (formData?: T): T | undefined => {\n const { schema, schemaUtils } = this.state;\n return schemaUtils.omitExtraData(schema, formData);\n };\n\n /** Allows a user to set a value for the provided `fieldPath`, which must be either a dotted path to the field OR a\n * `FieldPathList`. To set the root element, used either `''` or `[]` for the path. Passing undefined will clear the\n * value in the field.\n *\n * @param fieldPath - Either a dotted path to the field or the `FieldPathList` to the field\n * @param [newValue] - The new value for the field\n */\n setFieldValue = (fieldPath: string | FieldPathList, newValue?: T) => {\n const { registry } = this.state;\n const path = Array.isArray(fieldPath) ? fieldPath : fieldPath.split('.');\n const fieldPathId = toFieldPathId('', registry.globalFormOptions, path);\n this.onChange(newValue, path, undefined, fieldPathId[ID_KEY]);\n };\n\n /** Pushes the given change information into the `pendingChanges` array and then calls `processPendingChanges()` if\n * the array only contains a single pending change.\n *\n * @param newValue - The new form data from a change to a field\n * @param path - The path to the change into which to set the formData\n * @param [newErrorSchema] - The new `ErrorSchema` based on the field change\n * @param [id] - The id of the field that caused the change\n */\n onChange = (newValue: T | undefined, path: FieldPathList, newErrorSchema?: ErrorSchema<T>, id?: string) => {\n this.pendingChanges.push({ newValue, path, newErrorSchema, id });\n if (this.pendingChanges.length === 1) {\n this.processPendingChange();\n }\n };\n\n /** Function to handle changes made to a field in the `Form`. This handler gets the first change from the\n * `pendingChanges` list, containing the `newValue` for the `formData` and the `path` at which the `newValue` is to be\n * updated, along with a new, optional `ErrorSchema` for that same `path` and potentially the `id` of the field being\n * changed. It will first update the `formData` with any missing default fields and then, if `omitExtraData` and\n * `liveOmit` are turned on, the `formData` will be filtered to remove any extra data not in a form field. Then, the\n * resulting `formData` will be validated if required. The state will be updated with the new updated (potentially\n * filtered) `formData`, any errors that resulted from validation. Finally the `onChange` callback will be called, if\n * specified, with the updated state and the `processPendingChange()` function is called again.\n */\n processPendingChange() {\n if (this.pendingChanges.length === 0) {\n return;\n }\n // Mark that we're processing a user-initiated change.\n // This prevents componentDidUpdate from reverting oneOf/anyOf option switches.\n this._isProcessingUserChange = true;\n const { newValue, path, id } = this.pendingChanges[0];\n const { newErrorSchema } = this.pendingChanges[0];\n const { extraErrors, omitExtraData, liveOmit, noValidate, liveValidate, onChange } = this.props;\n const { formData: oldFormData, schemaUtils, schema, fieldPathId, schemaValidationErrorSchema, errors } = this.state;\n let { customErrors, errorSchema: originalErrorSchema } = this.state;\n const rootPathId = fieldPathId.path[0] || '';\n\n const isRootPath = !path || path.length === 0 || (path.length === 1 && path[0] === rootPathId);\n let retrievedSchema = this.state.retrievedSchema;\n let formData = isRootPath ? newValue : _cloneDeep(oldFormData);\n\n // When switching from null to an object option in oneOf, MultiSchemaField sends\n // an object with property names but undefined values (e.g., {types: undefined, content: undefined}).\n // In this case, pass undefined to getStateFromProps to trigger fresh default computation.\n // Only do this when the previous formData was null/undefined (switching FROM null).\n const hasOnlyUndefinedValues =\n isObject(formData) &&\n Object.keys(formData as object).length > 0 &&\n Object.values(formData as object).every((v) => v === undefined);\n const wasPreviouslyNull = oldFormData === null || oldFormData === undefined;\n const inputForDefaults = hasOnlyUndefinedValues && wasPreviouslyNull ? undefined : formData;\n\n if (isObject(formData) || Array.isArray(formData)) {\n if (newValue === ADDITIONAL_PROPERTY_KEY_REMOVE) {\n // For additional properties, we were given the special remove this key value, so unset it\n _unset(formData, path);\n } else if (!isRootPath) {\n // If the newValue is not on the root path, then set it into the form data\n _set(formData, path, newValue);\n }\n // Pass true to skip live validation in `getStateFromProps()` since we will do it a bit later\n const newState = this.getStateFromProps(this.props, inputForDefaults, undefined, undefined, undefined, true);\n formData = newState.formData;\n retrievedSchema = newState.retrievedSchema;\n }\n\n const mustValidate = !noValidate && (liveValidate === true || liveValidate === 'onChange');\n let state: Partial<FormState<T, S, F>> = { formData, schema };\n let newFormData = formData;\n\n if (omitExtraData === true && (liveOmit === true || liveOmit === 'onChange')) {\n newFormData = this.omitExtraData(formData);\n state = {\n formData: newFormData,\n };\n }\n\n if (newErrorSchema) {\n // First check to see if there is an existing validation error on this path...\n // @ts-expect-error TS2590, because getting from the error schema is confusing TS\n const oldValidationError = !isRootPath ? _get(schemaValidationErrorSchema, path) : schemaValidationErrorSchema;\n // If there is an old validation error for this path, assume we are updating it directly\n if (!_isEmpty(oldValidationError)) {\n // Update the originalErrorSchema \"in place\" or replace it if it is the root\n if (!isRootPath) {\n _set(originalErrorSchema, path, newErrorSchema);\n } else {\n originalErrorSchema = newErrorSchema;\n }\n } else {\n if (!customErrors) {\n customErrors = new ErrorSchemaBuilder<T>();\n }\n if (isRootPath) {\n const errors = _get(newErrorSchema, ERRORS_KEY);\n if (errors) {\n // only set errors when there are some\n customErrors.setErrors(errors);\n }\n } else {\n _set(customErrors.ErrorSchema, path, newErrorSchema);\n }\n }\n } else if (customErrors && _get(customErrors.ErrorSchema, [...path, ERRORS_KEY])) {\n // If we have custom errors and the path has an error, then we need to clear it\n customErrors.clearErrors(path);\n }\n // If there are pending changes in the queue, skip live validation since it will happen with the last change\n if (mustValidate && this.pendingChanges.length === 1) {\n const liveValidation = this.liveValidate(\n schema,\n schemaUtils,\n originalErrorSchema,\n newFormData,\n extraErrors,\n customErrors,\n retrievedSchema,\n );\n state = { formData: newFormData, ...liveValidation, customErrors };\n } else if (!noValidate && newErrorSchema) {\n // Merging 'newErrorSchema' into 'errorSchema' to display the custom raised errors.\n const mergedErrors = this.mergeErrors({ errorSchema: originalErrorSchema, errors }, extraErrors, customErrors