From b63ec99ad1cd2eb3f9ed98f44e5221163b563fc4 Mon Sep 17 00:00:00 2001 From: Heath C <51679588+heath-freenome@users.noreply.github.com> Date: Mon, 17 Oct 2022 17:29:41 -0700 Subject: [PATCH] fix: Partially fix #3189 (#3200) * fix: Partially fix #3189 - Updated `@rjsf/utils` to make the `schema` and `rootSchema` props use a new generic type `S` - Added the new `StrictRJSFSchema` type as the alias to `JSON7Schema` changing `RJSFSchema` to be `StrictRJSFSchema & GenericObjectType` - Deleted the `RJSFSchemaDefinition` type in favor of accessing it indirectly via the `S[""]` syntax - Added the new generic `S extends StrictRJSFSchema = RJSFSchema` to all types that directly or indirectly used `RJSFSchema` after the `T = any` type - Updated `SchemaUtilsType` to add the `F = any` generic to the whole interface, removing it from the definition of the two functions that need it - Updated all functions that used `RJSFSchema` to take the new generic, replacing `RJSFSchema` with `S` - Added missing generics where needed - Updated `@rjsf/core` to insert the `S extends StrictRJSFSchema = RJSFSchema` to every component that needed it, after the `T = any` generic - Updated the `index.ts` for the `ButtonTemplates`, `field`, `templates` and `widgets` to make them functions that take the `T`, `S` and `F` generics - Updated `getDefaultRegistry()` and `templates()` to call the appropriate functions - Replaced all uses of `RJSFSchema` and `RJSFSchemaDefinition` with `S` and `S[""]` - Added missing generics where needed - Fixed a few type casts due the to the change in the `RJSFSchema` type - Updated `@rjsf/validator-ajv6` to fix a few type casts due to the change in the `RJSFSchema` type - Updated `@rjsf/validator-ajv8` to add the `S extends StrictRJSFSchema = RJSFSchema` generic to the `customizeValidator()` function and the `AJV8Validator` class - Replaced all uses of `RJSFSchema` and `RJSFSchemaDefinition` with `S` and `S[""]` - Changed `RJSFSchema` to `S` where applicable - Fixed a few type casts due the to the change in the `RJSFSchema` type * Update packages/validator-ajv8/src/validator.ts Fix comment Co-authored-by: Nick Grosenbacher Co-authored-by: Nick Grosenbacher --- CHANGELOG.md | 18 + docs/5.x upgrade guide.md | 30 +- packages/core/src/components/Form.tsx | 99 +++-- .../core/src/components/fields/ArrayField.tsx | 103 ++--- .../src/components/fields/BooleanField.tsx | 28 +- .../components/fields/MultiSchemaField.tsx | 26 +- .../core/src/components/fields/NullField.tsx | 6 +- .../src/components/fields/NumberField.tsx | 13 +- .../src/components/fields/ObjectField.tsx | 26 +- .../src/components/fields/SchemaField.tsx | 52 ++- .../src/components/fields/StringField.tsx | 12 +- packages/core/src/components/fields/index.ts | 37 +- .../ArrayFieldDescriptionTemplate.tsx | 13 +- .../templates/ArrayFieldItemTemplate.tsx | 14 +- .../templates/ArrayFieldTemplate.tsx | 32 +- .../templates/ArrayFieldTitleTemplate.tsx | 16 +- .../templates/BaseInputTemplate.tsx | 20 +- .../templates/ButtonTemplates/AddButton.tsx | 12 +- .../templates/ButtonTemplates/IconButton.tsx | 26 +- .../ButtonTemplates/SubmitButton.tsx | 15 +- .../templates/ButtonTemplates/index.ts | 22 +- .../components/templates/DescriptionField.tsx | 14 +- .../src/components/templates/ErrorList.tsx | 13 +- .../templates/FieldErrorTemplate.tsx | 10 +- .../templates/FieldHelpTemplate.tsx | 10 +- .../templates/FieldTemplate/FieldTemplate.tsx | 25 +- .../templates/ObjectFieldTemplate.tsx | 15 +- .../src/components/templates/TitleField.tsx | 10 +- .../components/templates/UnsupportedField.tsx | 14 +- .../templates/WrapIfAdditionalTemplate.tsx | 10 +- .../core/src/components/templates/index.ts | 42 +- .../components/widgets/AltDateTimeWidget.tsx | 11 +- .../src/components/widgets/AltDateWidget.tsx | 30 +- .../src/components/widgets/CheckboxWidget.tsx | 17 +- .../components/widgets/CheckboxesWidget.tsx | 10 +- .../src/components/widgets/ColorWidget.tsx | 17 +- .../src/components/widgets/DateTimeWidget.tsx | 19 +- .../src/components/widgets/DateWidget.tsx | 15 +- .../src/components/widgets/EmailWidget.tsx | 17 +- .../src/components/widgets/FileWidget.tsx | 11 +- .../src/components/widgets/HiddenWidget.tsx | 8 +- .../src/components/widgets/PasswordWidget.tsx | 17 +- .../src/components/widgets/RadioWidget.tsx | 10 +- .../src/components/widgets/RangeWidget.tsx | 10 +- .../src/components/widgets/SelectWidget.tsx | 15 +- .../src/components/widgets/TextWidget.tsx | 15 +- .../src/components/widgets/TextareaWidget.tsx | 10 +- .../core/src/components/widgets/URLWidget.tsx | 15 +- .../src/components/widgets/UpDownWidget.tsx | 17 +- packages/core/src/components/widgets/index.ts | 48 ++- packages/core/src/getDefaultRegistry.ts | 19 +- packages/core/src/withTheme.tsx | 23 +- .../WrapIfAdditionalTemplate.tsx | 4 +- packages/utils/src/allowAdditionalItems.ts | 6 +- packages/utils/src/canExpand.ts | 14 +- packages/utils/src/createSchemaUtils.ts | 64 +-- packages/utils/src/findSchemaDefinition.ts | 13 +- packages/utils/src/getInputProps.ts | 15 +- packages/utils/src/getSchemaType.ts | 6 +- packages/utils/src/getSubmitButtonOptions.ts | 17 +- packages/utils/src/getTemplate.ts | 20 +- packages/utils/src/getUiOptions.ts | 12 +- packages/utils/src/getWidget.tsx | 33 +- packages/utils/src/hasWidget.ts | 17 +- packages/utils/src/isConstant.ts | 6 +- packages/utils/src/isCustomWidget.ts | 14 +- packages/utils/src/isFixedItems.ts | 6 +- .../utils/src/mergeDefaultsWithFormData.ts | 4 +- packages/utils/src/optionsList.ts | 12 +- packages/utils/src/processSelectValue.ts | 12 +- packages/utils/src/rangeSpec.ts | 6 +- .../utils/src/schema/getDefaultFormState.ts | 99 +++-- packages/utils/src/schema/getDisplayLabel.ts | 27 +- .../utils/src/schema/getMatchingOption.ts | 13 +- packages/utils/src/schema/isFilesArray.ts | 25 +- packages/utils/src/schema/isMultiSelect.ts | 13 +- packages/utils/src/schema/isSelect.ts | 18 +- .../utils/src/schema/mergeValidationData.ts | 15 +- packages/utils/src/schema/retrieveSchema.ts | 242 ++++++------ packages/utils/src/schema/toIdSchema.ts | 31 +- packages/utils/src/schema/toPathSchema.ts | 29 +- packages/utils/src/schemaRequiresTrueValue.ts | 14 +- packages/utils/src/toConstant.ts | 6 +- packages/utils/src/types.ts | 364 +++++++++++------- packages/utils/test/getTemplate.test.ts | 3 +- .../utils/test/schema/retrieveSchemaTest.ts | 3 +- packages/validator-ajv6/src/validator.ts | 4 +- .../validator-ajv6/test/validator.test.ts | 2 +- .../validator-ajv8/src/customizeValidator.ts | 11 +- packages/validator-ajv8/src/validator.ts | 46 ++- .../validator-ajv8/test/validator.test.ts | 2 +- 91 files changed, 1488 insertions(+), 897 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3907c9fe83..ac0549d27b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,24 @@ should change the heading of the (upcoming) version to include a major version b ## @rjsf/chakra-ui - Automatically close single-choice Select widget on selection +## @rjsf/core +- Added the new generic, `S extends StrictRJSFSchema = RJSFSchema`, for `schema`/`rootSchema` to every component that needed it. + +## @rjsf/utils +- Beta-only potentially BREAKING CHANGE: Changed all types that directly or indirectly defined `schema`/`rootSchema` to add the generic `S extends StrictRJSFSchema = RJSFSchema` and use `S` as the type for them. + - `StrictRJSFSchema` was added as the alias to `JSON7Schema` and `RJSFSchema` was modified to be `StrictRJSFSchema & GenericObjectType` + - This new generic was added BEFORE the newly added `F = any` generic because it is assumed that more people will want to change the schema than the formContext types + - This provides future support for the newer draft versions of the schema + +## @rjsf/validator-ajv6 +- Fixed a few type casts given the new expanded definition of the `RJSFSchema` type change + +## @rjsf/validator-ajv8 +- Updated the typing to add the new `S extends StrictRJSFSchema = RJSFSchema` generic and fixed up type casts + +## Dev / docs / playground +- Updated the `5.x upgrade guide` to document the new `StrictRJSFSchema` and `S` generic + # 5.0.0-beta.11 ## @rjsf/antd diff --git a/docs/5.x upgrade guide.md b/docs/5.x upgrade guide.md index c4a92977f6..f801bf71bc 100644 --- a/docs/5.x upgrade guide.md +++ b/docs/5.x upgrade guide.md @@ -42,11 +42,11 @@ All the rest of the types for RJSF are now exported from the new `@rjsf/utils` p NOTE: The types in `@rjsf/utils` have been improved significantly from those in version 4. Some of the most notable changes are: -- `RJSFSchema` has replaced the use of `JSON7Schema` for future compatibility reasons. - - Currently `RJSFSchema` is simply an alias to `JSON7Schema` so this change is purely a naming one. - - It is highly recommended to update your use of `JSON7Schema` with `RJSFSchema` so that when the RJSF begins supporting a newer JSON Schema version out-of-the-box, your code won't be affected. -- `RJSFSchemaDefinition` has replaced the use of `JSONSchema7Definition` for the same reasons. - The use of the generic `T` (defaulting to `any`) for the `formData` type has been expanded to cover all type hierarchies that use `formData`. +- `StrictRJSFSchema` and `RJSFSchema` have replaced the use of `JSON7Schema` for future compatibility reasons. + - `RJSFSchema` is `StrictRJSFSchema` joined with the `GenericObjectType` (i.e. `{ [key: string]: any }`) to allow for additional syntax related to newer draft versions + - All definitions of `schema` and `rootSchema` elements have been replaced with a generic that is defined as `S extends StrictRJSFSchema = RJSFSchema` + - It is highly recommended to update your use of `JSON7Schema` with `RJSFSchema` since that is the default for the new generic used for `schema` and `rootSchema` - A new generic `F` (defaulting to `any`) was added for the `formContext` type, and all types in the hierarchy that use `formContext` have had that generic added to them. - The new `CustomValidator`, `ErrorTransformer`, `ValidationData`, `ValidatorType` and `SchemaUtilsType` types were added to support the decoupling of the validation implementation. - The new `TemplatesType`, `ArrayFieldDescriptionProps`, `ArrayFieldTitleProps`, `UnsupportedFieldProps`, `IconButtonProps`, `SubmitButtonProps` and `UIOptionsBaseType` were added to support the consolidation (and expansion) of `templates` in the `Registry` and `Form`. @@ -362,21 +362,21 @@ For example, given a schema such as: { "properties": { "foo": { - "type": "string", - }, - }, + "type": "string" + } + } }, { "properties": { "bar": { - "type": "string", - }, - }, - }, - ], - }, - }, - }, + "type": "string" + } + } + } + ] + } + } + } } ``` diff --git a/packages/core/src/components/Form.tsx b/packages/core/src/components/Form.tsx index 448d4c1a70..e8b877e127 100644 --- a/packages/core/src/components/Form.tsx +++ b/packages/core/src/components/Form.tsx @@ -7,6 +7,7 @@ import { IdSchema, PathSchema, RJSFSchema, + StrictRJSFSchema, RJSFValidationError, Registry, RegistryWidgetsType, @@ -33,15 +34,19 @@ import _isEmpty from "lodash/isEmpty"; import getDefaultRegistry from "../getDefaultRegistry"; /** The properties that are passed to the `Form` */ -export interface FormProps { +export interface FormProps< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +> { /** The JSON schema object for the form */ - schema: RJSFSchema; + schema: S; /** An implementation of the `ValidatorType` interface that is needed for form validation to work */ - validator: ValidatorType; + validator: ValidatorType; /** The optional children for the form, if provided, it will replace the default `SubmitButton` */ children?: React.ReactNode; /** The uiSchema for the form */ - uiSchema?: UiSchema; + uiSchema?: UiSchema; /** The data for the form, used to prefill a form with existing data */ formData?: T; // Form presentation and behavior modifiers @@ -71,19 +76,19 @@ export interface FormProps { readonly?: boolean; // Form registry /** The dictionary of registered fields in the form */ - fields?: RegistryFieldsType; + fields?: RegistryFieldsType; /** The dictionary of registered templates in the form; Partial allows a subset to be provided beyond the defaults */ - templates?: Partial, "ButtonTemplates">> & { - ButtonTemplates?: Partial["ButtonTemplates"]>; + templates?: Partial, "ButtonTemplates">> & { + ButtonTemplates?: Partial["ButtonTemplates"]>; }; /** The dictionary of registered widgets in the form */ - widgets?: RegistryWidgetsType; + widgets?: RegistryWidgetsType; // Callbacks /** If you plan on being notified every time the form data are updated, you can pass an `onChange` handler, which will * receive the same args as `onSubmit` any time a value is updated in the form. Can also return the `id` of the field * that caused the change */ - onChange?: (data: IChangeEvent, id?: string) => void; + onChange?: (data: IChangeEvent, id?: string) => void; /** To react when submitted form data are invalid, pass an `onError` handler. It will be passed the list of * encountered errors */ @@ -92,7 +97,7 @@ export interface FormProps { * and its data are valid. It will be passed a result object having a `formData` attribute, which is the valid form * data you're usually after. The original event will also be passed as a second parameter */ - onSubmit?: (data: IChangeEvent, event: React.FormEvent) => void; + onSubmit?: (data: IChangeEvent, event: React.FormEvent) => void; /** Sometimes you may want to trigger events or modify external state when a field has been touched, so you can pass * an `onBlur` handler, which will receive the id of the input that was blurred and the field value */ @@ -184,17 +189,21 @@ export interface FormProps { } /** The data that is contained within the state for the `Form` */ -export interface FormState { +export interface FormState< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +> { /** The JSON schema object for the form */ - schema: RJSFSchema; + schema: S; /** The uiSchema for the form */ - uiSchema: UiSchema; + uiSchema: UiSchema; /** The `IdSchema` for the form, computed from the `schema`, the `rootFieldId`, the `formData` and the `idPrefix` and * `idSeparator` props. */ idSchema: IdSchema; /** The schemaUtils implementation used by the `Form`, created from the `validator` and the `schema` */ - schemaUtils: SchemaUtilsType; + schemaUtils: SchemaUtilsType; /** The current data for the form, computed from the `formData` prop and the changes made by the user */ formData: T; /** Flag indicating whether the form is in edit mode, true when `formData` is passed to the form, otherwise false */ @@ -214,9 +223,12 @@ export interface FormState { /** The event data passed when changes have been made to the form, includes everything from the `FormState` except * the schema validation errors. An additional `status` is added when returned from `onSubmit` */ -export interface IChangeEvent - extends Omit< - FormState, +export interface IChangeEvent< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +> extends Omit< + FormState, "schemaValidationErrors" | "schemaValidationErrorSchema" > { /** The status of the form when submitted */ @@ -224,10 +236,11 @@ export interface IChangeEvent } /** The `Form` component renders the outer form and all the fields defined in the `schema` */ -export default class Form extends Component< - FormProps, - FormState -> { +export default class Form< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +> extends Component, FormState> { /** The ref used to hold the `form` element, this needs to be `any` because `tagName` or `_internalFormWrapper` can * provide any possible type here */ @@ -239,7 +252,7 @@ export default class Form extends Component< * * @param props - The initial props for the `Form` */ - constructor(props: FormProps) { + constructor(props: FormProps) { super(props); if (!props.validator) { @@ -262,7 +275,7 @@ export default class Form extends Component< * * @param nextProps - The new set of props about to be applied to the `Form` */ - UNSAFE_componentWillReceiveProps(nextProps: FormProps) { + UNSAFE_componentWillReceiveProps(nextProps: FormProps) { const nextState = this.getStateFromProps(nextProps, nextProps.formData); if ( !deepEquals(nextState.formData, nextProps.formData) && @@ -283,24 +296,24 @@ export default class Form extends Component< * @returns - The new state for the `Form` */ getStateFromProps( - props: FormProps, + props: FormProps, inputFormData?: T - ): FormState { - const state: FormState = this.state || {}; + ): FormState { + const state: FormState = this.state || {}; const schema = "schema" in props ? props.schema : this.props.schema; - const uiSchema: UiSchema = + const uiSchema: UiSchema = ("uiSchema" in props ? props.uiSchema! : this.props.uiSchema!) || {}; const edit = typeof inputFormData !== "undefined"; const liveValidate = "liveValidate" in props ? props.liveValidate : this.props.liveValidate; const mustValidate = edit && !props.noValidate && liveValidate; const rootSchema = schema; - let schemaUtils: SchemaUtilsType = state.schemaUtils; + let schemaUtils: SchemaUtilsType = state.schemaUtils; if ( !schemaUtils || schemaUtils.doesSchemaUtilsDiffer(props.validator, rootSchema) ) { - schemaUtils = createSchemaUtils(props.validator, rootSchema); + schemaUtils = createSchemaUtils(props.validator, rootSchema); } const formData: T = schemaUtils.getDefaultFormState( schema, @@ -355,7 +368,7 @@ export default class Form extends Component< props.idPrefix, props.idSeparator ); - const nextState: FormState = { + const nextState: FormState = { schemaUtils, schema, uiSchema, @@ -377,8 +390,8 @@ export default class Form extends Component< * @returns - True if the component should be updated, false otherwise */ shouldComponentUpdate( - nextProps: FormProps, - nextState: FormState + nextProps: FormProps, + nextState: FormState ): boolean { return shouldRender(this, nextProps, nextState); } @@ -393,7 +406,7 @@ export default class Form extends Component< validate( formData: T, schema = this.props.schema, - altSchemaUtils?: SchemaUtilsType + altSchemaUtils?: SchemaUtilsType ): ValidationData { const schemaUtils = altSchemaUtils ? altSchemaUtils @@ -411,11 +424,11 @@ export default class Form extends Component< } /** Renders any errors contained in the `state` in using the `ErrorList`, if not disabled by `showErrorList`. */ - renderErrors(registry: Registry) { + renderErrors(registry: Registry) { const { errors, errorSchema, schema, uiSchema } = this.state; const { showErrorList, formContext } = this.props; - const options = getUiOptions(uiSchema); - const ErrorListTemplate = getTemplate<"ErrorListTemplate", T, F>( + const options = getUiOptions(uiSchema); + const ErrorListTemplate = getTemplate<"ErrorListTemplate", T, S, F>( "ErrorListTemplate", registry, options @@ -522,7 +535,7 @@ export default class Form extends Component< } const mustValidate = !noValidate && liveValidate; - let state: Partial> = { formData, schema }; + let state: Partial> = { formData, schema }; let newFormData = formData; if (omitExtraData === true && liveOmit === true) { @@ -573,7 +586,7 @@ export default class Form extends Component< }; } this.setState( - state as FormState, + state as FormState, () => onChange && onChange({ ...this.state, ...state }, id) ); }; @@ -664,9 +677,13 @@ export default class Form extends Component< }; /** Returns the registry for the form */ - getRegistry(): Registry { + getRegistry(): Registry { const { schemaUtils } = this.state; - const { fields, templates, widgets, formContext } = getDefaultRegistry(); + const { fields, templates, widgets, formContext } = getDefaultRegistry< + T, + S, + F + >(); return { fields: { ...fields, ...this.props.fields }, templates: { @@ -769,7 +786,7 @@ export default class Form extends Component< const { SchemaField: _SchemaField } = registry.fields; const { SubmitButton } = registry.templates.ButtonTemplates; // The `semantic-ui` and `material-ui` themes have `_internalFormWrapper`s that take an `as` prop that is the - // PropTypes.elementType to use for the inner tag so we'll need to pass `tagName` along if it is provided. + // PropTypes.elementType to use for the inner tag, so we'll need to pass `tagName` along if it is provided. // NOTE, the `as` prop is native to `semantic-ui` and is emulated in the `material-ui` theme const as = _internalFormWrapper ? tagName : undefined; const FormTag = _internalFormWrapper || tagName || "form"; diff --git a/packages/core/src/components/fields/ArrayField.tsx b/packages/core/src/components/fields/ArrayField.tsx index ba45cb45c9..2211b1ae12 100644 --- a/packages/core/src/components/fields/ArrayField.tsx +++ b/packages/core/src/components/fields/ArrayField.tsx @@ -9,10 +9,10 @@ import { optionsList, ArrayFieldTemplateProps, ErrorSchema, - Field, FieldProps, IdSchema, RJSFSchema, + StrictRJSFSchema, UiSchema, ITEMS_KEY, } from "@rjsf/utils"; @@ -70,15 +70,16 @@ function keyedToPlainFormData( /** The `ArrayField` component is used to render a field in the schema that is of type `array`. It supports both normal * and fixed array, allowing user to add and remove elements from the array data. */ -class ArrayField extends Component< - FieldProps, - ArrayFieldState -> { +class ArrayField< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +> extends Component, ArrayFieldState> { /** Constructs an `ArrayField` from the `props`, generating the initial keyed data from the `formData` * * @param props - The `FieldProps` for this template */ - constructor(props: FieldProps) { + constructor(props: FieldProps) { super(props); const { formData = [] } = props; const keyedFormData = generateKeyedFormData(formData); @@ -94,8 +95,12 @@ class ArrayField extends Component< * @param nextProps - The next set of props data * @param prevState - The previous set of state data */ - static getDerivedStateFromProps( - nextProps: Readonly>, + static getDerivedStateFromProps< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any + >( + nextProps: Readonly>, prevState: Readonly> ) { // Don't call getDerivedStateFromProps if keyed formdata was just updated. @@ -140,7 +145,7 @@ class ArrayField extends Component< * @param itemSchema - The schema for the item * @return - True if the item schema type does not contain the "null" type */ - isItemRequired(itemSchema: RJSFSchema) { + isItemRequired(itemSchema: S) { if (Array.isArray(itemSchema.type)) { // While we don't yet support composite/nullable jsonschema types, it's // future-proof to check for requirement against these. @@ -159,7 +164,7 @@ class ArrayField extends Component< */ canAddItem(formItems: any[]) { const { schema, uiSchema } = this.props; - let { addable } = getUiOptions(uiSchema); + let { addable } = getUiOptions(uiSchema); if (addable !== false) { // if ui:options.addable was not explicitly set to false, we can add // another item if we have not exceeded maxItems yet @@ -178,11 +183,11 @@ class ArrayField extends Component< _getNewFormDataRow = (): T => { const { schema, registry } = this.props; const { schemaUtils } = registry; - let itemSchema = schema.items as RJSFSchema; + let itemSchema = schema.items as S; if (isFixedItems(schema) && allowAdditionalItems(schema)) { - itemSchema = schema.additionalItems as RJSFSchema; + itemSchema = schema.additionalItems as S; } - // Cast this as a T to work around schema utils being for T[] caused by the FieldProps call on the class + // Cast this as a T to work around schema utils being for T[] caused by the FieldProps call on the class return schemaUtils.getDefaultFormState(itemSchema) as unknown as T; }; @@ -377,10 +382,11 @@ class ArrayField extends Component< const { schema, uiSchema, idSchema, registry } = this.props; const { schemaUtils } = registry; if (!(ITEMS_KEY in schema)) { - const uiOptions = getUiOptions(uiSchema); + const uiOptions = getUiOptions(uiSchema); const UnsupportedFieldTemplate = getTemplate< "UnsupportedFieldTemplate", T[], + S, F >("UnsupportedFieldTemplate", registry, uiOptions); @@ -397,7 +403,7 @@ class ArrayField extends Component< // If array has enum or uniqueItems set to true, call renderMultiSelect() to render the default multiselect widget or a custom widget, if specified. return this.renderMultiSelect(); } - if (isCustomWidget(uiSchema)) { + if (isCustomWidget(uiSchema)) { return this.renderCustomWidget(); } if (isFixedItems(schema)) { @@ -432,13 +438,13 @@ class ArrayField extends Component< const { keyedFormData } = this.state; const title = schema.title === undefined ? name : schema.title; const { schemaUtils, formContext } = registry; - const uiOptions = getUiOptions(uiSchema); - const _schemaItems = isObject(schema.items) - ? (schema.items as RJSFSchema) - : {}; - const itemsSchema = schemaUtils.retrieveSchema(_schemaItems); + const uiOptions = getUiOptions(uiSchema); + const _schemaItems: S = isObject(schema.items) + ? (schema.items as S) + : ({} as S); + const itemsSchema: S = schemaUtils.retrieveSchema(_schemaItems); const formData = keyedToPlainFormData(this.state.keyedFormData); - const arrayProps: ArrayFieldTemplateProps = { + const arrayProps: ArrayFieldTemplateProps = { canAdd: this.canAddItem(formData), items: keyedFormData.map((keyedItem, index) => { const { key, item } = keyedItem; @@ -462,7 +468,7 @@ class ArrayField extends Component< name: name && `${name}-${index}`, canMoveUp: index > 0, canMoveDown: index < formData.length - 1, - itemSchema: itemSchema, + itemSchema, itemIdSchema, itemErrorSchema, itemData: itemCast, @@ -488,7 +494,7 @@ class ArrayField extends Component< registry, }; - const Template = getTemplate<"ArrayFieldTemplate", T[], F>( + const Template = getTemplate<"ArrayFieldTemplate", T[], S, F>( "ArrayFieldTemplate", registry, uiOptions @@ -519,8 +525,8 @@ class ArrayField extends Component< const { widgets, formContext } = registry; const title = schema.title || name; - const { widget, ...options } = getUiOptions(uiSchema); - const Widget = getWidget(schema, widget, widgets); + const { widget, ...options } = getUiOptions(uiSchema); + const Widget = getWidget(schema, widget, widgets); return ( extends Component< name, } = this.props; const { widgets, schemaUtils, formContext } = registry; - const itemsSchema = schemaUtils.retrieveSchema( - schema.items as RJSFSchema, - items - ); + const itemsSchema = schemaUtils.retrieveSchema(schema.items as S, items); const title = schema.title || name; const enumOptions = optionsList(itemsSchema); - const { widget = "select", ...options } = getUiOptions(uiSchema); - const Widget = getWidget(schema, widget, widgets); + const { widget = "select", ...options } = getUiOptions(uiSchema); + const Widget = getWidget(schema, widget, widgets); return ( extends Component< } = this.props; const title = schema.title || name; const { widgets, formContext } = registry; - const { widget = "files", ...options } = getUiOptions(uiSchema); - const Widget = getWidget(schema, widget, widgets); + const { widget = "files", ...options } = getUiOptions(uiSchema); + const Widget = getWidget(schema, widget, widgets); return ( extends Component< const { keyedFormData } = this.state; let { formData: items = [] } = this.props; const title = schema.title || name; - const uiOptions = getUiOptions(uiSchema); + const uiOptions = getUiOptions(uiSchema); const { schemaUtils, formContext } = registry; - const _schemaItems = isObject(schema.items) - ? (schema.items as RJSFSchema[]) - : []; - const itemSchemas = _schemaItems.map((item: RJSFSchema, index: number) => + const _schemaItems: S[] = isObject(schema.items) + ? (schema.items as S[]) + : ([] as S[]); + const itemSchemas = _schemaItems.map((item: S, index: number) => schemaUtils.retrieveSchema(item, formData[index] as unknown as T[]) ); const additionalSchema = isObject(schema.additionalItems) - ? schemaUtils.retrieveSchema(schema.additionalItems, formData) + ? schemaUtils.retrieveSchema(schema.additionalItems as S, formData) : null; if (!items || items.length < itemSchemas.length) { @@ -687,7 +690,7 @@ class ArrayField extends Component< } // These are the props passed into the render function - const arrayProps: ArrayFieldTemplateProps = { + const arrayProps: ArrayFieldTemplateProps = { canAdd: this.canAddItem(items) && !!additionalSchema, className: "field field-array field-array-fixed-items", disabled, @@ -700,7 +703,7 @@ class ArrayField extends Component< const additional = index >= itemSchemas.length; const itemSchema = additional && isObject(schema.additionalItems) - ? schemaUtils.retrieveSchema(schema.additionalItems, itemCast) + ? schemaUtils.retrieveSchema(schema.additionalItems as S, itemCast) : itemSchemas[index]; const itemIdPrefix = idSchema.$id + idSeparator + index; const itemIdSchema = schemaUtils.toIdSchema( @@ -748,7 +751,7 @@ class ArrayField extends Component< rawErrors, }; - const Template = getTemplate<"ArrayFieldTemplate", T[], F>( + const Template = getTemplate<"ArrayFieldTemplate", T[], S, F>( "ArrayFieldTemplate", registry, uiOptions @@ -768,14 +771,14 @@ class ArrayField extends Component< canRemove?: boolean; canMoveUp?: boolean; canMoveDown?: boolean; - itemSchema: RJSFSchema; + itemSchema: S; itemData: T[]; - itemUiSchema: UiSchema; + itemUiSchema: UiSchema; itemIdSchema: IdSchema; itemErrorSchema?: ErrorSchema; autofocus?: boolean; - onBlur: FieldProps["onBlur"]; - onFocus: FieldProps["onFocus"]; + onBlur: FieldProps["onBlur"]; + onFocus: FieldProps["onFocus"]; rawErrors?: string[]; }) { const { @@ -809,7 +812,7 @@ class ArrayField extends Component< fields: { ArraySchemaField, SchemaField }, } = registry; const ItemSchemaField = ArraySchemaField || SchemaField; - const { orderable = true, removable = true } = getUiOptions( + const { orderable = true, removable = true } = getUiOptions( uiSchema ); const has: { [key: string]: boolean } = { @@ -863,7 +866,7 @@ class ArrayField extends Component< } } -/** `ArrayField` is `React.ComponentType>` (necessarily) but the `registry` requires things to be a - * `Field` which is defined as `React.ComponentType>`, so cast it to make `registry` happy. +/** `ArrayField` is `React.ComponentType>` (necessarily) but the `registry` requires things to be a + * `Field` which is defined as `React.ComponentType>`, so cast it to make `registry` happy. */ -export default ArrayField as unknown as Field; +export default ArrayField; diff --git a/packages/core/src/components/fields/BooleanField.tsx b/packages/core/src/components/fields/BooleanField.tsx index 352c91f529..789a61a02a 100644 --- a/packages/core/src/components/fields/BooleanField.tsx +++ b/packages/core/src/components/fields/BooleanField.tsx @@ -4,9 +4,9 @@ import { getUiOptions, optionsList, FieldProps, - RJSFSchemaDefinition, EnumOptionsType, RJSFSchema, + StrictRJSFSchema, } from "@rjsf/utils"; import isObject from "lodash/isObject"; @@ -15,7 +15,11 @@ import isObject from "lodash/isObject"; * * @param props - The `FieldProps` for this template */ -function BooleanField(props: FieldProps) { +function BooleanField< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +>(props: FieldProps) { const { schema, name, @@ -34,15 +38,15 @@ function BooleanField(props: FieldProps) { } = props; const { title } = schema; const { widgets, formContext } = registry; - const { widget = "checkbox", ...options } = getUiOptions(uiSchema); + const { widget = "checkbox", ...options } = getUiOptions(uiSchema); const Widget = getWidget(schema, widget, widgets); - let enumOptions: EnumOptionsType[] | undefined; + let enumOptions: EnumOptionsType[] | undefined; if (Array.isArray(schema.oneOf)) { - enumOptions = optionsList({ + enumOptions = optionsList({ oneOf: schema.oneOf - .map((option: RJSFSchemaDefinition) => { + .map((option) => { if (isObject(option)) { return { ...option, @@ -51,16 +55,16 @@ function BooleanField(props: FieldProps) { } return undefined; }) - .filter((o) => o) as RJSFSchemaDefinition[], // cast away the error that typescript can't grok is fixed - }); + .filter((o: any) => o) as S[], // cast away the error that typescript can't grok is fixed + } as unknown as S); } else { // We deprecated enumNames in v5. It's intentionally omitted from RSJFSchema type, so we need to cast here. - const schemaWithEnumNames = schema as RJSFSchema & { enumNames?: string[] }; + const schemaWithEnumNames = schema as S & { enumNames?: string[] }; const enums = schema.enum ?? [true, false]; if ( !schemaWithEnumNames.enumNames && enums.length === 2 && - enums.every((v) => typeof v === "boolean") + enums.every((v: any) => typeof v === "boolean") ) { enumOptions = [ { @@ -73,11 +77,11 @@ function BooleanField(props: FieldProps) { }, ]; } else { - enumOptions = optionsList({ + enumOptions = optionsList({ enum: enums, // NOTE: enumNames is deprecated, but still supported for now. enumNames: schemaWithEnumNames.enumNames, - } as RJSFSchema); + } as unknown as S); } } diff --git a/packages/core/src/components/fields/MultiSchemaField.tsx b/packages/core/src/components/fields/MultiSchemaField.tsx index 23d2bf3309..8b91eee520 100644 --- a/packages/core/src/components/fields/MultiSchemaField.tsx +++ b/packages/core/src/components/fields/MultiSchemaField.tsx @@ -6,6 +6,7 @@ import { deepEquals, FieldProps, RJSFSchema, + StrictRJSFSchema, } from "@rjsf/utils"; import unset from "lodash/unset"; @@ -20,15 +21,16 @@ type AnyOfFieldState = { * * @param props - The `FieldProps` for this template */ -class AnyOfField extends Component< - FieldProps, - AnyOfFieldState -> { +class AnyOfField< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +> extends Component, AnyOfFieldState> { /** Constructs an `AnyOfField` with the given `props` to initialize the initially selected option in state * * @param props - The `FieldProps` for this template */ - constructor(props: FieldProps) { + constructor(props: FieldProps) { super(props); const { formData, options } = this.props; @@ -45,7 +47,7 @@ class AnyOfField extends Component< * @param prevState - The previous `AnyOfFieldState` for this template */ componentDidUpdate( - prevProps: Readonly>, + prevProps: Readonly>, prevState: Readonly ) { const { formData, options, idSchema } = this.props; @@ -76,11 +78,7 @@ class AnyOfField extends Component< * @param options - The list of options to choose from * @return - The index of the `option` that best matches the `formData` */ - getMatchingOption( - selectedOption: number, - formData: T, - options: RJSFSchema[] - ) { + getMatchingOption(selectedOption: number, formData: T, options: S[]) { const { schemaUtils } = this.props.registry; const option = schemaUtils.getMatchingOption(formData, options); @@ -178,8 +176,8 @@ class AnyOfField extends Component< const { widgets, fields } = registry; const { SchemaField: _SchemaField } = fields; const { selectedOption } = this.state; - const { widget = "select", ...uiOptions } = getUiOptions(uiSchema); - const Widget = getWidget({ type: "number" }, widget, widgets); + const { widget = "select", ...uiOptions } = getUiOptions(uiSchema); + const Widget = getWidget({ type: "number" }, widget, widgets); const option = options[selectedOption] || null; let optionSchema; @@ -202,7 +200,7 @@ class AnyOfField extends Component<
(props: FieldProps) { +function NullField( + props: FieldProps +) { const { formData, onChange } = props; useEffect(() => { if (formData === undefined) { diff --git a/packages/core/src/components/fields/NumberField.tsx b/packages/core/src/components/fields/NumberField.tsx index 6ab99f0fe0..cfbaa4a1d4 100644 --- a/packages/core/src/components/fields/NumberField.tsx +++ b/packages/core/src/components/fields/NumberField.tsx @@ -1,5 +1,10 @@ import React, { useState, useCallback } from "react"; -import { asNumber, FieldProps } from "@rjsf/utils"; +import { + asNumber, + FieldProps, + RJSFSchema, + StrictRJSFSchema, +} from "@rjsf/utils"; // Matches a string that ends in a . character, optionally followed by a sequence of // digits followed by any number of 0 characters up until the end of the line. @@ -30,7 +35,9 @@ const trailingCharMatcher = /[0.]0*$/; * value cached in the state. If it matches the cached value, the cached * value is passed to the input instead of the formData value */ -function NumberField(props: FieldProps) { +function NumberField( + props: FieldProps +) { const { registry, onChange, formData, value: initialValue } = props; const [lastValue, setLastValue] = useState(initialValue); const { StringField } = registry.fields; @@ -42,7 +49,7 @@ function NumberField(props: FieldProps) { * @param value - The current value for the change occurring */ const handleChange = useCallback( - (value: FieldProps["value"]) => { + (value: FieldProps["value"]) => { // Cache the original value in component state setLastValue(value); diff --git a/packages/core/src/components/fields/ObjectField.tsx b/packages/core/src/components/fields/ObjectField.tsx index 89317401dc..633ccc9cdb 100644 --- a/packages/core/src/components/fields/ObjectField.tsx +++ b/packages/core/src/components/fields/ObjectField.tsx @@ -8,6 +8,7 @@ import { GenericObjectType, IdSchema, RJSFSchema, + StrictRJSFSchema, ADDITIONAL_PROPERTY_FLAG, PROPERTIES_KEY, REF_KEY, @@ -31,10 +32,11 @@ type ObjectFieldState = { * * @param props - The `FieldProps` for this template */ -class ObjectField extends Component< - FieldProps, - ObjectFieldState -> { +class ObjectField< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +> extends Component, ObjectFieldState> { /** Set up the initial state */ state = { wasPropertyKeyModified: false, @@ -112,7 +114,9 @@ class ObjectField extends Component< */ getAvailableKey = (preferredKey: string, formData: T) => { const { uiSchema } = this.props; - const { duplicateKeySuffixSeparator = "-" } = getUiOptions(uiSchema); + const { duplicateKeySuffixSeparator = "-" } = getUiOptions( + uiSchema + ); let index = 0; let newKey = preferredKey; @@ -188,7 +192,7 @@ class ObjectField extends Component< * * @param schema - The schema element to which the new property is being added */ - handleAddClick = (schema: RJSFSchema) => () => { + handleAddClick = (schema: S) => () => { if (!schema.additionalProperties) { return; } @@ -201,7 +205,7 @@ class ObjectField extends Component< if (REF_KEY in schema.additionalProperties) { const { schemaUtils } = registry; const refSchema = schemaUtils.retrieveSchema( - { $ref: schema.additionalProperties[REF_KEY] }, + { $ref: schema.additionalProperties[REF_KEY] } as S, formData ); type = refSchema.type; @@ -238,8 +242,8 @@ class ObjectField extends Component< const { fields, formContext, schemaUtils } = registry; const { SchemaField } = fields; - const schema = schemaUtils.retrieveSchema(rawSchema, formData); - const uiOptions = getUiOptions(uiSchema); + const schema: S = schemaUtils.retrieveSchema(rawSchema, formData); + const uiOptions = getUiOptions(uiSchema); const { properties: schemaProperties = {} } = schema; const title = schema.title === undefined ? name : schema.title; @@ -260,7 +264,7 @@ class ObjectField extends Component< ); } - const Template = getTemplate<"ObjectFieldTemplate", T, F>( + const Template = getTemplate<"ObjectFieldTemplate", T, S, F>( "ObjectFieldTemplate", registry, uiOptions @@ -278,7 +282,7 @@ class ObjectField extends Component< const fieldUiSchema = addedByAdditionalProperties ? uiSchema.additionalProperties : uiSchema[name]; - const hidden = getUiOptions(fieldUiSchema).widget === "hidden"; + const hidden = getUiOptions(fieldUiSchema).widget === "hidden"; const fieldIdSchema: IdSchema = get(idSchema, [name], {}); return { diff --git a/packages/core/src/components/fields/SchemaField.tsx b/packages/core/src/components/fields/SchemaField.tsx index 923bbbf902..4298d72bf6 100644 --- a/packages/core/src/components/fields/SchemaField.tsx +++ b/packages/core/src/components/fields/SchemaField.tsx @@ -11,7 +11,7 @@ import { IdSchema, Registry, RJSFSchema, - RJSFSchemaDefinition, + StrictRJSFSchema, UIOptionsType, ID_KEY, ADDITIONAL_PROPERTY_FLAG, @@ -40,11 +40,15 @@ const COMPONENT_TYPES: { [key: string]: string } = { * @param registry - The registry from which fields and templates are obtained * @returns - The `Field` component that is used to render the actual field data */ -function getFieldComponent( - schema: RJSFSchema, - uiOptions: UIOptionsType, +function getFieldComponent< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +>( + schema: S, + uiOptions: UIOptionsType, idSchema: IdSchema, - registry: Registry + registry: Registry ) { const field = uiOptions.field; const { fields } = registry; @@ -73,6 +77,7 @@ function getFieldComponent( const UnsupportedFieldTemplate = getTemplate< "UnsupportedFieldTemplate", T, + S, F >("UnsupportedFieldTemplate", registry, uiOptions); @@ -93,7 +98,11 @@ function getFieldComponent( * * @param props - The `FieldProps` for this component */ -function SchemaFieldRender(props: FieldProps) { +function SchemaFieldRender< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +>(props: FieldProps) { const { schema: _schema, idSchema: _idSchema, @@ -111,8 +120,8 @@ function SchemaFieldRender(props: FieldProps) { wasPropertyKeyModified = false, } = props; const { formContext, schemaUtils } = registry; - const uiOptions = getUiOptions(uiSchema); - const FieldTemplate = getTemplate<"FieldTemplate", T, F>( + const uiOptions = getUiOptions(uiSchema); + const FieldTemplate = getTemplate<"FieldTemplate", T, S, F>( "FieldTemplate", registry, uiOptions @@ -120,14 +129,15 @@ function SchemaFieldRender(props: FieldProps) { const DescriptionFieldTemplate = getTemplate< "DescriptionFieldTemplate", T, + S, F >("DescriptionFieldTemplate", registry, uiOptions); - const FieldHelpTemplate = getTemplate<"FieldHelpTemplate", T, F>( + const FieldHelpTemplate = getTemplate<"FieldHelpTemplate", T, S, F>( "FieldHelpTemplate", registry, uiOptions ); - const FieldErrorTemplate = getTemplate<"FieldErrorTemplate", T, F>( + const FieldErrorTemplate = getTemplate<"FieldErrorTemplate", T, S, F>( "FieldErrorTemplate", registry, uiOptions @@ -150,7 +160,7 @@ function SchemaFieldRender(props: FieldProps) { [fieldId, onChange] ); - const FieldComponent = getFieldComponent( + const FieldComponent = getFieldComponent( schema, uiOptions, idSchema, @@ -259,7 +269,7 @@ function SchemaFieldRender(props: FieldProps) { registry={registry} /> ); - const fieldProps: Omit, "children"> = { + const fieldProps: Omit, "children"> = { description: ( (props: FieldProps) { onBlur={props.onBlur} onChange={props.onChange} onFocus={props.onFocus} - options={schema.anyOf.map((_schema: RJSFSchemaDefinition) => + options={schema.anyOf.map((_schema) => schemaUtils.retrieveSchema( - isObject(_schema) ? _schema : {}, + isObject(_schema) ? (_schema as S) : ({} as S), formData ) )} @@ -351,9 +361,9 @@ function SchemaFieldRender(props: FieldProps) { onBlur={props.onBlur} onChange={props.onChange} onFocus={props.onFocus} - options={schema.oneOf.map((_schema: RJSFSchemaDefinition) => + options={schema.oneOf.map((_schema) => schemaUtils.retrieveSchema( - isObject(_schema) ? _schema : {}, + isObject(_schema) ? (_schema as S) : ({} as S), formData ) )} @@ -371,13 +381,17 @@ function SchemaFieldRender(props: FieldProps) { /** The `SchemaField` component determines whether it is necessary to rerender the component based on any props changes * and if so, calls the `SchemaFieldRender` component with the props. */ -class SchemaField extends React.Component> { - shouldComponentUpdate(nextProps: Readonly>) { +class SchemaField< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +> extends React.Component> { + shouldComponentUpdate(nextProps: Readonly>) { return !deepEquals(this.props, nextProps); } render() { - return {...this.props} />; + return {...this.props} />; } } diff --git a/packages/core/src/components/fields/StringField.tsx b/packages/core/src/components/fields/StringField.tsx index b3aa0d93db..3aaea89e88 100644 --- a/packages/core/src/components/fields/StringField.tsx +++ b/packages/core/src/components/fields/StringField.tsx @@ -5,13 +5,17 @@ import { optionsList, hasWidget, FieldProps, + RJSFSchema, + StrictRJSFSchema, } from "@rjsf/utils"; /** The `StringField` component is used to render a schema field that represents a string type * * @param props - The `FieldProps` for this template */ -function StringField(props: FieldProps) { +function StringField( + props: FieldProps +) { const { schema, name, @@ -34,15 +38,15 @@ function StringField(props: FieldProps) { ? optionsList(schema) : undefined; let defaultWidget = enumOptions ? "select" : "text"; - if (format && hasWidget(schema, format, widgets)) { + if (format && hasWidget(schema, format, widgets)) { defaultWidget = format; } const { widget = defaultWidget, placeholder = "", ...options - } = getUiOptions(uiSchema); - const Widget = getWidget(schema, widget, widgets); + } = getUiOptions(uiSchema); + const Widget = getWidget(schema, widget, widgets); return ( (): RegistryFieldsType { + return { + AnyOfField: MultiSchemaField, + ArrayField: ArrayField as unknown as Field, + // ArrayField falls back to SchemaField if ArraySchemaField is not defined, which it isn't by default + BooleanField, + NumberField, + ObjectField, + OneOfField: MultiSchemaField, + SchemaField, + StringField, + NullField, + }; +} export default fields; diff --git a/packages/core/src/components/templates/ArrayFieldDescriptionTemplate.tsx b/packages/core/src/components/templates/ArrayFieldDescriptionTemplate.tsx index 43b1098e4a..db54c0b1f2 100644 --- a/packages/core/src/components/templates/ArrayFieldDescriptionTemplate.tsx +++ b/packages/core/src/components/templates/ArrayFieldDescriptionTemplate.tsx @@ -3,6 +3,8 @@ import { getTemplate, getUiOptions, ArrayFieldDescriptionProps, + RJSFSchema, + StrictRJSFSchema, } from "@rjsf/utils"; /** The `ArrayFieldDescriptionTemplate` component renders a `DescriptionFieldTemplate` with an `id` derived from @@ -10,11 +12,13 @@ import { * * @param props - The `ArrayFieldDescriptionProps` for the component */ -export default function ArrayFieldDescriptionTemplate( - props: ArrayFieldDescriptionProps -) { +export default function ArrayFieldDescriptionTemplate< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +>(props: ArrayFieldDescriptionProps) { const { idSchema, description, registry, schema, uiSchema } = props; - const options = getUiOptions(uiSchema); + const options = getUiOptions(uiSchema); const { label: displayLabel = true } = options; if (!description || !displayLabel) { return null; @@ -22,6 +26,7 @@ export default function ArrayFieldDescriptionTemplate( const DescriptionFieldTemplate = getTemplate< "DescriptionFieldTemplate", T, + S, F >("DescriptionFieldTemplate", registry, options); const id = `${idSchema.$id}__description`; diff --git a/packages/core/src/components/templates/ArrayFieldItemTemplate.tsx b/packages/core/src/components/templates/ArrayFieldItemTemplate.tsx index 402ccbe509..076a8b98d3 100644 --- a/packages/core/src/components/templates/ArrayFieldItemTemplate.tsx +++ b/packages/core/src/components/templates/ArrayFieldItemTemplate.tsx @@ -1,13 +1,19 @@ import React, { CSSProperties } from "react"; -import { ArrayFieldTemplateItemType } from "@rjsf/utils"; +import { + ArrayFieldTemplateItemType, + RJSFSchema, + StrictRJSFSchema, +} from "@rjsf/utils"; /** The `ArrayFieldItemTemplate` component is the template used to render an items of an array. * * @param props - The `ArrayFieldTemplateItemType` props for the component */ -export default function ArrayFieldItemTemplate( - props: ArrayFieldTemplateItemType -) { +export default function ArrayFieldItemTemplate< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +>(props: ArrayFieldTemplateItemType) { const { children, className, diff --git a/packages/core/src/components/templates/ArrayFieldTemplate.tsx b/packages/core/src/components/templates/ArrayFieldTemplate.tsx index f9a6bf5b75..710c1865d6 100644 --- a/packages/core/src/components/templates/ArrayFieldTemplate.tsx +++ b/packages/core/src/components/templates/ArrayFieldTemplate.tsx @@ -4,15 +4,19 @@ import { getUiOptions, ArrayFieldTemplateProps, ArrayFieldTemplateItemType, + RJSFSchema, + StrictRJSFSchema, } from "@rjsf/utils"; /** The `ArrayFieldTemplate` component is the template used to render all items in an array. * * @param props - The `ArrayFieldTemplateItemType` props for the component */ -export default function ArrayFieldTemplate( - props: ArrayFieldTemplateProps -) { +export default function ArrayFieldTemplate< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +>(props: ArrayFieldTemplateProps) { const { canAdd, className, @@ -27,22 +31,24 @@ export default function ArrayFieldTemplate( schema, title, } = props; - const uiOptions = getUiOptions(uiSchema); + const uiOptions = getUiOptions(uiSchema); const ArrayFieldDescriptionTemplate = getTemplate< "ArrayFieldDescriptionTemplate", T, + S, F >("ArrayFieldDescriptionTemplate", registry, uiOptions); - const ArrayFieldItemTemplate = getTemplate<"ArrayFieldItemTemplate", T, F>( + const ArrayFieldItemTemplate = getTemplate<"ArrayFieldItemTemplate", T, S, F>( "ArrayFieldItemTemplate", registry, uiOptions ); - const ArrayFieldTitleTemplate = getTemplate<"ArrayFieldTitleTemplate", T, F>( + const ArrayFieldTitleTemplate = getTemplate< "ArrayFieldTitleTemplate", - registry, - uiOptions - ); + T, + S, + F + >("ArrayFieldTitleTemplate", registry, uiOptions); // Button templates are not overridden in the uiSchema const { ButtonTemplates: { AddButton }, @@ -66,9 +72,11 @@ export default function ArrayFieldTemplate( />
{items && - items.map(({ key, ...itemProps }: ArrayFieldTemplateItemType) => ( - - ))} + items.map( + ({ key, ...itemProps }: ArrayFieldTemplateItemType) => ( + + ) + )}
{canAdd && ( ( - props: ArrayFieldTitleProps -) { +export default function ArrayFieldTitleTemplate< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +>(props: ArrayFieldTitleProps) { const { idSchema, title, schema, uiSchema, required, registry } = props; - const options = getUiOptions(uiSchema); + const options = getUiOptions(uiSchema); const { label: displayLabel = true } = options; if (!title || !displayLabel) { return null; } - const TitleFieldTemplate: TemplatesType["TitleFieldTemplate"] = - getTemplate<"TitleFieldTemplate", T, F>( + const TitleFieldTemplate: TemplatesType["TitleFieldTemplate"] = + getTemplate<"TitleFieldTemplate", T, S, F>( "TitleFieldTemplate", registry, options diff --git a/packages/core/src/components/templates/BaseInputTemplate.tsx b/packages/core/src/components/templates/BaseInputTemplate.tsx index 752dc851cf..5094b41391 100644 --- a/packages/core/src/components/templates/BaseInputTemplate.tsx +++ b/packages/core/src/components/templates/BaseInputTemplate.tsx @@ -1,5 +1,10 @@ import React, { useCallback } from "react"; -import { getInputProps, WidgetProps } from "@rjsf/utils"; +import { + getInputProps, + RJSFSchema, + StrictRJSFSchema, + WidgetProps, +} from "@rjsf/utils"; /** The `BaseInputTemplate` is the template to use to render the basic `` component for the `core` theme. * It is used as the template for rendering many of the based widgets that differ by `type` and callbacks only. @@ -7,9 +12,11 @@ import { getInputProps, WidgetProps } from "@rjsf/utils"; * * @param props - The `WidgetProps` for this template */ -export default function BaseInputTemplate( - props: WidgetProps -) { +export default function BaseInputTemplate< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +>(props: WidgetProps) { const { id, value, @@ -35,7 +42,10 @@ export default function BaseInputTemplate( console.log("No id for", props); throw new Error(`no id for props ${JSON.stringify(props)}`); } - const inputProps = { ...rest, ...getInputProps(schema, type, options) }; + const inputProps = { + ...rest, + ...getInputProps(schema, type, options), + }; let inputValue; if (inputProps.type === "number" || inputProps.type === "integer") { diff --git a/packages/core/src/components/templates/ButtonTemplates/AddButton.tsx b/packages/core/src/components/templates/ButtonTemplates/AddButton.tsx index 84cd04f734..7f8ca7367f 100644 --- a/packages/core/src/components/templates/ButtonTemplates/AddButton.tsx +++ b/packages/core/src/components/templates/ButtonTemplates/AddButton.tsx @@ -1,15 +1,15 @@ import React from "react"; -import { IconButtonProps } from "@rjsf/utils"; +import { IconButtonProps, RJSFSchema, StrictRJSFSchema } from "@rjsf/utils"; import IconButton from "./IconButton"; /** The `AddButton` renders a button that represent the `Add` action on a form */ -export default function AddButton({ - className, - onClick, - disabled, -}: IconButtonProps) { +export default function AddButton< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +>({ className, onClick, disabled }: IconButtonProps) { return (

diff --git a/packages/core/src/components/templates/ButtonTemplates/IconButton.tsx b/packages/core/src/components/templates/ButtonTemplates/IconButton.tsx index 89115307c2..5d9e3a8788 100644 --- a/packages/core/src/components/templates/ButtonTemplates/IconButton.tsx +++ b/packages/core/src/components/templates/ButtonTemplates/IconButton.tsx @@ -1,7 +1,11 @@ import React from "react"; -import { IconButtonProps } from "@rjsf/utils"; +import { IconButtonProps, RJSFSchema, StrictRJSFSchema } from "@rjsf/utils"; -export default function IconButton(props: IconButtonProps) { +export default function IconButton< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +>(props: IconButtonProps) { const { iconType = "default", icon, @@ -20,7 +24,11 @@ export default function IconButton(props: IconButtonProps) { ); } -export function MoveDownButton(props: IconButtonProps) { +export function MoveDownButton< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +>(props: IconButtonProps) { return ( (props: IconButtonProps) { return ( (props: IconButtonProps) { return ( ({ - uiSchema, -}: SubmitButtonProps) { +export default function SubmitButton< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +>({ uiSchema }: SubmitButtonProps) { const { submitText, norender, diff --git a/packages/core/src/components/templates/ButtonTemplates/index.ts b/packages/core/src/components/templates/ButtonTemplates/index.ts index 30d483daea..5d22d8e5c9 100644 --- a/packages/core/src/components/templates/ButtonTemplates/index.ts +++ b/packages/core/src/components/templates/ButtonTemplates/index.ts @@ -1,15 +1,21 @@ -import { TemplatesType } from "@rjsf/utils"; +import { RJSFSchema, StrictRJSFSchema, TemplatesType } from "@rjsf/utils"; import SubmitButton from "./SubmitButton"; import AddButton from "./AddButton"; import { RemoveButton, MoveDownButton, MoveUpButton } from "./IconButton"; -const buttonTemplates: TemplatesType["ButtonTemplates"] = { - SubmitButton, - AddButton, - MoveDownButton, - MoveUpButton, - RemoveButton, -}; +function buttonTemplates< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +>(): TemplatesType["ButtonTemplates"] { + return { + SubmitButton, + AddButton, + MoveDownButton, + MoveUpButton, + RemoveButton, + }; +} export default buttonTemplates; diff --git a/packages/core/src/components/templates/DescriptionField.tsx b/packages/core/src/components/templates/DescriptionField.tsx index d360ebdf13..61a0afa379 100644 --- a/packages/core/src/components/templates/DescriptionField.tsx +++ b/packages/core/src/components/templates/DescriptionField.tsx @@ -1,13 +1,19 @@ import React from "react"; -import { DescriptionFieldProps } from "@rjsf/utils"; +import { + DescriptionFieldProps, + RJSFSchema, + StrictRJSFSchema, +} from "@rjsf/utils"; /** The `DescriptionField` is the template to use to render the description of a field * * @param props - The `DescriptionFieldProps` for this component */ -export default function DescriptionField( - props: DescriptionFieldProps -) { +export default function DescriptionField< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +>(props: DescriptionFieldProps) { const { id, description } = props; if (!description) { return null; diff --git a/packages/core/src/components/templates/ErrorList.tsx b/packages/core/src/components/templates/ErrorList.tsx index ea9931bad0..93b9ebe42b 100644 --- a/packages/core/src/components/templates/ErrorList.tsx +++ b/packages/core/src/components/templates/ErrorList.tsx @@ -1,11 +1,20 @@ import React from "react"; -import { ErrorListProps, RJSFValidationError } from "@rjsf/utils"; +import { + ErrorListProps, + RJSFValidationError, + RJSFSchema, + StrictRJSFSchema, +} from "@rjsf/utils"; /** The `ErrorList` component is the template that renders the all the errors associated with the fields in the `Form` * * @param props - The `ErrorListProps` for this component */ -export default function ErrorList({ errors }: ErrorListProps) { +export default function ErrorList< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +>({ errors }: ErrorListProps) { return (

diff --git a/packages/core/src/components/templates/FieldErrorTemplate.tsx b/packages/core/src/components/templates/FieldErrorTemplate.tsx index 3ad2b5b3f8..8889f8f8ce 100644 --- a/packages/core/src/components/templates/FieldErrorTemplate.tsx +++ b/packages/core/src/components/templates/FieldErrorTemplate.tsx @@ -1,13 +1,15 @@ import React from "react"; -import { FieldErrorProps } from "@rjsf/utils"; +import { FieldErrorProps, RJSFSchema, StrictRJSFSchema } from "@rjsf/utils"; /** The `FieldErrorTemplate` component renders the errors local to the particular field * * @param props - The `FieldErrorProps` for the errors being rendered */ -export default function FieldErrorTemplate( - props: FieldErrorProps -) { +export default function FieldErrorTemplate< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +>(props: FieldErrorProps) { const { errors = [], idSchema } = props; if (errors.length === 0) { return null; diff --git a/packages/core/src/components/templates/FieldHelpTemplate.tsx b/packages/core/src/components/templates/FieldHelpTemplate.tsx index 8d8b3a9620..5cf711d651 100644 --- a/packages/core/src/components/templates/FieldHelpTemplate.tsx +++ b/packages/core/src/components/templates/FieldHelpTemplate.tsx @@ -1,13 +1,15 @@ import React from "react"; -import { FieldHelpProps } from "@rjsf/utils"; +import { FieldHelpProps, RJSFSchema, StrictRJSFSchema } from "@rjsf/utils"; /** The `FieldHelpTemplate` component renders any help desired for a field * * @param props - The `FieldHelpProps` to be rendered */ -export default function FieldHelpTemplate( - props: FieldHelpProps -) { +export default function FieldHelpTemplate< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +>(props: FieldHelpProps) { const { idSchema, help } = props; if (!help) { return null; diff --git a/packages/core/src/components/templates/FieldTemplate/FieldTemplate.tsx b/packages/core/src/components/templates/FieldTemplate/FieldTemplate.tsx index 24e7d1832f..464fc29d0d 100644 --- a/packages/core/src/components/templates/FieldTemplate/FieldTemplate.tsx +++ b/packages/core/src/components/templates/FieldTemplate/FieldTemplate.tsx @@ -1,5 +1,11 @@ import React from "react"; -import { FieldTemplateProps, getTemplate, getUiOptions } from "@rjsf/utils"; +import { + FieldTemplateProps, + RJSFSchema, + StrictRJSFSchema, + getTemplate, + getUiOptions, +} from "@rjsf/utils"; import Label from "./Label"; @@ -8,9 +14,11 @@ import Label from "./Label"; * * @param props - The `FieldTemplateProps` for this component */ -export default function FieldTemplate( - props: FieldTemplateProps -) { +export default function FieldTemplate< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +>(props: FieldTemplateProps) { const { id, label, @@ -25,11 +33,12 @@ export default function FieldTemplate( uiSchema, } = props; const uiOptions = getUiOptions(uiSchema); - const WrapIfAdditionalTemplate = getTemplate<"WrapIfAdditionalTemplate">( + const WrapIfAdditionalTemplate = getTemplate< "WrapIfAdditionalTemplate", - registry, - uiOptions - ); + T, + S, + F + >("WrapIfAdditionalTemplate", registry, uiOptions); if (hidden) { return
{children}
; } diff --git a/packages/core/src/components/templates/ObjectFieldTemplate.tsx b/packages/core/src/components/templates/ObjectFieldTemplate.tsx index 45bd936ad9..0ecdaf9d3c 100644 --- a/packages/core/src/components/templates/ObjectFieldTemplate.tsx +++ b/packages/core/src/components/templates/ObjectFieldTemplate.tsx @@ -2,6 +2,8 @@ import React from "react"; import { ObjectFieldTemplatePropertyType, ObjectFieldTemplateProps, + RJSFSchema, + StrictRJSFSchema, canExpand, getTemplate, getUiOptions, @@ -13,9 +15,11 @@ import { * * @param props - The `ObjectFieldTemplateProps` for this component */ -export default function ObjectFieldTemplate( - props: ObjectFieldTemplateProps -) { +export default function ObjectFieldTemplate< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +>(props: ObjectFieldTemplateProps) { const { description, disabled, @@ -30,8 +34,8 @@ export default function ObjectFieldTemplate( title, uiSchema, } = props; - const options = getUiOptions(uiSchema); - const TitleFieldTemplate = getTemplate<"TitleFieldTemplate", T, F>( + const options = getUiOptions(uiSchema); + const TitleFieldTemplate = getTemplate<"TitleFieldTemplate", T, S, F>( "TitleFieldTemplate", registry, options @@ -39,6 +43,7 @@ export default function ObjectFieldTemplate( const DescriptionFieldTemplate = getTemplate< "DescriptionFieldTemplate", T, + S, F >("DescriptionFieldTemplate", registry, options); // Button templates are not overridden in the uiSchema diff --git a/packages/core/src/components/templates/TitleField.tsx b/packages/core/src/components/templates/TitleField.tsx index a62437647e..2eec0ef8be 100644 --- a/packages/core/src/components/templates/TitleField.tsx +++ b/packages/core/src/components/templates/TitleField.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { TitleFieldProps } from "@rjsf/utils"; +import { TitleFieldProps, RJSFSchema, StrictRJSFSchema } from "@rjsf/utils"; const REQUIRED_FIELD_SYMBOL = "*"; @@ -7,9 +7,11 @@ const REQUIRED_FIELD_SYMBOL = "*"; * * @param props - The `TitleFieldProps` for this component */ -export default function TitleField( - props: TitleFieldProps -) { +export default function TitleField< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +>(props: TitleFieldProps) { const { id, title, required } = props; return ( diff --git a/packages/core/src/components/templates/UnsupportedField.tsx b/packages/core/src/components/templates/UnsupportedField.tsx index 91f822a490..cfd5797512 100644 --- a/packages/core/src/components/templates/UnsupportedField.tsx +++ b/packages/core/src/components/templates/UnsupportedField.tsx @@ -1,14 +1,20 @@ import React from "react"; -import { UnsupportedFieldProps } from "@rjsf/utils"; +import { + UnsupportedFieldProps, + RJSFSchema, + StrictRJSFSchema, +} from "@rjsf/utils"; /** The `UnsupportedField` component is used to render a field in the schema is one that is not supported by * react-jsonschema-form. * * @param props - The `FieldProps` for this template */ -function UnsupportedField( - props: UnsupportedFieldProps -) { +function UnsupportedField< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +>(props: UnsupportedFieldProps) { const { schema, idSchema, reason } = props; return (
diff --git a/packages/core/src/components/templates/WrapIfAdditionalTemplate.tsx b/packages/core/src/components/templates/WrapIfAdditionalTemplate.tsx index 622b0cfe58..4041416dab 100644 --- a/packages/core/src/components/templates/WrapIfAdditionalTemplate.tsx +++ b/packages/core/src/components/templates/WrapIfAdditionalTemplate.tsx @@ -1,6 +1,8 @@ import React from "react"; import { ADDITIONAL_PROPERTY_FLAG, + RJSFSchema, + StrictRJSFSchema, WrapIfAdditionalTemplateProps, } from "@rjsf/utils"; @@ -11,9 +13,11 @@ import Label from "./FieldTemplate/Label"; * * @param props - The `WrapIfAdditionalProps` for this component */ -export default function WrapIfAdditionalTemplate( - props: WrapIfAdditionalTemplateProps -) { +export default function WrapIfAdditionalTemplate< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +>(props: WrapIfAdditionalTemplateProps) { const { id, classNames, diff --git a/packages/core/src/components/templates/index.ts b/packages/core/src/components/templates/index.ts index 214ea41cde..ab929e01ea 100644 --- a/packages/core/src/components/templates/index.ts +++ b/packages/core/src/components/templates/index.ts @@ -1,4 +1,4 @@ -import { TemplatesType } from "@rjsf/utils"; +import { RJSFSchema, StrictRJSFSchema, TemplatesType } from "@rjsf/utils"; import ArrayFieldDescriptionTemplate from "./ArrayFieldDescriptionTemplate"; import ArrayFieldItemTemplate from "./ArrayFieldItemTemplate"; @@ -16,22 +16,28 @@ import TitleField from "./TitleField"; import UnsupportedField from "./UnsupportedField"; import WrapIfAdditionalTemplate from "./WrapIfAdditionalTemplate"; -const templates: TemplatesType = { - ArrayFieldDescriptionTemplate, - ArrayFieldItemTemplate, - ArrayFieldTemplate, - ArrayFieldTitleTemplate, - ButtonTemplates, - BaseInputTemplate, - DescriptionFieldTemplate: DescriptionField, - ErrorListTemplate: ErrorList, - FieldTemplate, - FieldErrorTemplate, - FieldHelpTemplate, - ObjectFieldTemplate, - TitleFieldTemplate: TitleField, - UnsupportedFieldTemplate: UnsupportedField, - WrapIfAdditionalTemplate, -}; +function templates< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +>(): TemplatesType { + return { + ArrayFieldDescriptionTemplate, + ArrayFieldItemTemplate, + ArrayFieldTemplate, + ArrayFieldTitleTemplate, + ButtonTemplates: ButtonTemplates(), + BaseInputTemplate, + DescriptionFieldTemplate: DescriptionField, + ErrorListTemplate: ErrorList, + FieldTemplate, + FieldErrorTemplate, + FieldHelpTemplate, + ObjectFieldTemplate, + TitleFieldTemplate: TitleField, + UnsupportedFieldTemplate: UnsupportedField, + WrapIfAdditionalTemplate, + }; +} export default templates; diff --git a/packages/core/src/components/widgets/AltDateTimeWidget.tsx b/packages/core/src/components/widgets/AltDateTimeWidget.tsx index 25b464d86c..de53994b49 100644 --- a/packages/core/src/components/widgets/AltDateTimeWidget.tsx +++ b/packages/core/src/components/widgets/AltDateTimeWidget.tsx @@ -1,4 +1,4 @@ -import { WidgetProps } from "@rjsf/utils"; +import { RJSFSchema, StrictRJSFSchema, WidgetProps } from "@rjsf/utils"; import React from "react"; /** The `AltDateTimeWidget` is an alternative widget for rendering datetime properties. @@ -6,10 +6,11 @@ import React from "react"; * * @param props - The `WidgetProps` for this component */ -function AltDateTimeWidget({ - time = true, - ...props -}: WidgetProps) { +function AltDateTimeWidget< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +>({ time = true, ...props }: WidgetProps) { const { AltDateWidget } = props.registry.widgets; return ; } diff --git a/packages/core/src/components/widgets/AltDateWidget.tsx b/packages/core/src/components/widgets/AltDateWidget.tsx index 9f6c6b6b3c..022eae0cfb 100644 --- a/packages/core/src/components/widgets/AltDateWidget.tsx +++ b/packages/core/src/components/widgets/AltDateWidget.tsx @@ -4,8 +4,10 @@ import { parseDateString, toDateString, pad, - WidgetProps, DateObject, + RJSFSchema, + StrictRJSFSchema, + WidgetProps, } from "@rjsf/utils"; function rangeOptions(start: number, stop: number) { @@ -45,8 +47,12 @@ function dateElementProps( return data; } -type DateElementProps = Pick< - WidgetProps, +type DateElementProps< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +> = Pick< + WidgetProps, | "value" | "disabled" | "readonly" @@ -61,7 +67,11 @@ type DateElementProps = Pick< range: [number, number]; }; -function DateElement({ +function DateElement< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +>({ type, range, value, @@ -73,12 +83,12 @@ function DateElement({ registry, onBlur, onFocus, -}: DateElementProps) { +}: DateElementProps) { const id = rootId + "_" + type; const { SelectWidget } = registry.widgets; return ( ({ /** The `AltDateWidget` is an alternative widget for rendering date properties. * @param props - The `WidgetProps` for this component */ -function AltDateWidget({ +function AltDateWidget< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +>({ time = false, disabled = false, readonly = false, @@ -111,7 +125,7 @@ function AltDateWidget({ onFocus, onChange, value, -}: WidgetProps) { +}: WidgetProps) { const [state, setState] = useReducer( (state: DateObject, action: Partial) => { return { ...state, ...action }; diff --git a/packages/core/src/components/widgets/CheckboxWidget.tsx b/packages/core/src/components/widgets/CheckboxWidget.tsx index 38123db344..5cd2b74a09 100644 --- a/packages/core/src/components/widgets/CheckboxWidget.tsx +++ b/packages/core/src/components/widgets/CheckboxWidget.tsx @@ -1,12 +1,22 @@ import React, { useCallback } from "react"; -import { getTemplate, schemaRequiresTrueValue, WidgetProps } from "@rjsf/utils"; +import { + getTemplate, + schemaRequiresTrueValue, + RJSFSchema, + StrictRJSFSchema, + WidgetProps, +} from "@rjsf/utils"; /** The `CheckBoxWidget` is a widget for rendering boolean properties. * It is typically used to represent a boolean. * * @param props - The `WidgetProps` for this component */ -function CheckboxWidget({ +function CheckboxWidget< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +>({ schema, uiSchema, options, @@ -20,10 +30,11 @@ function CheckboxWidget({ onFocus, onChange, registry, -}: WidgetProps) { +}: WidgetProps) { const DescriptionFieldTemplate = getTemplate< "DescriptionFieldTemplate", T, + S, F >("DescriptionFieldTemplate", registry, options); // Because an unchecked checkbox will cause html5 validation to fail, only add diff --git a/packages/core/src/components/widgets/CheckboxesWidget.tsx b/packages/core/src/components/widgets/CheckboxesWidget.tsx index c3a521bafc..a941677035 100644 --- a/packages/core/src/components/widgets/CheckboxesWidget.tsx +++ b/packages/core/src/components/widgets/CheckboxesWidget.tsx @@ -1,5 +1,5 @@ import React, { ChangeEvent } from "react"; -import { WidgetProps } from "@rjsf/utils"; +import { WidgetProps, RJSFSchema, StrictRJSFSchema } from "@rjsf/utils"; function selectValue(value: any, selected: any[], all: any[]) { const at = all.indexOf(value); @@ -18,7 +18,11 @@ function deselectValue(value: any, selected: any[]) { * * @param props - The `WidgetProps` for this component */ -function CheckboxesWidget({ +function CheckboxesWidget< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +>({ id, disabled, options: { inline = false, enumOptions, enumDisabled }, @@ -26,7 +30,7 @@ function CheckboxesWidget({ autofocus = false, readonly, onChange, -}: WidgetProps) { +}: WidgetProps) { return (
{Array.isArray(enumOptions) && diff --git a/packages/core/src/components/widgets/ColorWidget.tsx b/packages/core/src/components/widgets/ColorWidget.tsx index 9362bcd565..473425c8e1 100644 --- a/packages/core/src/components/widgets/ColorWidget.tsx +++ b/packages/core/src/components/widgets/ColorWidget.tsx @@ -1,16 +1,23 @@ import React from "react"; -import { getTemplate, WidgetProps } from "@rjsf/utils"; +import { + getTemplate, + RJSFSchema, + StrictRJSFSchema, + WidgetProps, +} from "@rjsf/utils"; /** The `ColorWidget` component uses the `BaseInputTemplate` changing the type to `color` and disables it when it is * either disabled or readonly. * * @param props - The `WidgetProps` for this component */ -export default function ColorWidget( - props: WidgetProps -) { +export default function ColorWidget< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +>(props: WidgetProps) { const { disabled, readonly, options, registry } = props; - const BaseInputTemplate = getTemplate<"BaseInputTemplate", T, F>( + const BaseInputTemplate = getTemplate<"BaseInputTemplate", T, S, F>( "BaseInputTemplate", registry, options diff --git a/packages/core/src/components/widgets/DateTimeWidget.tsx b/packages/core/src/components/widgets/DateTimeWidget.tsx index e4088a423f..7fc260b50d 100644 --- a/packages/core/src/components/widgets/DateTimeWidget.tsx +++ b/packages/core/src/components/widgets/DateTimeWidget.tsx @@ -1,16 +1,25 @@ import React from "react"; -import { getTemplate, localToUTC, utcToLocal, WidgetProps } from "@rjsf/utils"; +import { + getTemplate, + localToUTC, + utcToLocal, + RJSFSchema, + StrictRJSFSchema, + WidgetProps, +} from "@rjsf/utils"; /** The `DateTimeWidget` component uses the `BaseInputTemplate` changing the type to `datetime-local` and transforms * the value to/from utc using the appropriate utility functions. * * @param props - The `WidgetProps` for this component */ -export default function DateTimeWidget( - props: WidgetProps -) { +export default function DateTimeWidget< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +>(props: WidgetProps) { const { onChange, value, options, registry } = props; - const BaseInputTemplate = getTemplate<"BaseInputTemplate", T, F>( + const BaseInputTemplate = getTemplate<"BaseInputTemplate", T, S, F>( "BaseInputTemplate", registry, options diff --git a/packages/core/src/components/widgets/DateWidget.tsx b/packages/core/src/components/widgets/DateWidget.tsx index 75799e520c..87291d8b10 100644 --- a/packages/core/src/components/widgets/DateWidget.tsx +++ b/packages/core/src/components/widgets/DateWidget.tsx @@ -1,14 +1,23 @@ import React, { useCallback } from "react"; -import { getTemplate, WidgetProps } from "@rjsf/utils"; +import { + getTemplate, + RJSFSchema, + StrictRJSFSchema, + WidgetProps, +} from "@rjsf/utils"; /** The `DateWidget` component uses the `BaseInputTemplate` changing the type to `date` and transforms * the value to undefined when it is falsy during the `onChange` handling. * * @param props - The `WidgetProps` for this component */ -export default function DateWidget(props: WidgetProps) { +export default function DateWidget< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +>(props: WidgetProps) { const { onChange, options, registry } = props; - const BaseInputTemplate = getTemplate<"BaseInputTemplate", T, F>( + const BaseInputTemplate = getTemplate<"BaseInputTemplate", T, S, F>( "BaseInputTemplate", registry, options diff --git a/packages/core/src/components/widgets/EmailWidget.tsx b/packages/core/src/components/widgets/EmailWidget.tsx index 498b1f3a0c..c0fd138395 100644 --- a/packages/core/src/components/widgets/EmailWidget.tsx +++ b/packages/core/src/components/widgets/EmailWidget.tsx @@ -1,15 +1,22 @@ import React from "react"; -import { getTemplate, WidgetProps } from "@rjsf/utils"; +import { + getTemplate, + RJSFSchema, + StrictRJSFSchema, + WidgetProps, +} from "@rjsf/utils"; /** The `EmailWidget` component uses the `BaseInputTemplate` changing the type to `email`. * * @param props - The `WidgetProps` for this component */ -export default function EmailWidget( - props: WidgetProps -) { +export default function EmailWidget< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +>(props: WidgetProps) { const { options, registry } = props; - const BaseInputTemplate = getTemplate<"BaseInputTemplate", T, F>( + const BaseInputTemplate = getTemplate<"BaseInputTemplate", T, S, F>( "BaseInputTemplate", registry, options diff --git a/packages/core/src/components/widgets/FileWidget.tsx b/packages/core/src/components/widgets/FileWidget.tsx index 15a1df365e..87095c0df9 100644 --- a/packages/core/src/components/widgets/FileWidget.tsx +++ b/packages/core/src/components/widgets/FileWidget.tsx @@ -1,6 +1,11 @@ import React, { ChangeEvent, useCallback, useMemo, useState } from "react"; -import { dataURItoBlob, WidgetProps } from "@rjsf/utils"; +import { + dataURItoBlob, + RJSFSchema, + StrictRJSFSchema, + WidgetProps, +} from "@rjsf/utils"; function addNameToDataURL(dataURL: string, name: string) { if (dataURL === null) { @@ -85,7 +90,7 @@ function extractFileInfo(dataURLs: string[]) { * The `FileWidget` is a widget for rendering file upload fields. * It is typically used with a string property with data-url format. */ -function FileWidget({ +function FileWidget({ multiple, id, readonly, @@ -94,7 +99,7 @@ function FileWidget({ value, autofocus = false, options, -}: WidgetProps) { +}: WidgetProps) { const extractedFilesInfo = useMemo( () => Array.isArray(value) ? extractFileInfo(value) : extractFileInfo([value]), diff --git a/packages/core/src/components/widgets/HiddenWidget.tsx b/packages/core/src/components/widgets/HiddenWidget.tsx index 9cf150e9f2..d2a64d4cdd 100644 --- a/packages/core/src/components/widgets/HiddenWidget.tsx +++ b/packages/core/src/components/widgets/HiddenWidget.tsx @@ -1,12 +1,16 @@ import React from "react"; -import { WidgetProps } from "@rjsf/utils"; +import { RJSFSchema, StrictRJSFSchema, WidgetProps } from "@rjsf/utils"; /** The `HiddenWidget` is a widget for rendering a hidden input field. * It is typically used by setting type to "hidden". * * @param props - The `WidgetProps` for this component */ -function HiddenWidget({ id, value }: WidgetProps) { +function HiddenWidget< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +>({ id, value }: WidgetProps) { return ( ( - props: WidgetProps -) { +export default function PasswordWidget< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +>(props: WidgetProps) { const { options, registry } = props; - const BaseInputTemplate = getTemplate<"BaseInputTemplate", T, F>( + const BaseInputTemplate = getTemplate<"BaseInputTemplate", T, S, F>( "BaseInputTemplate", registry, options diff --git a/packages/core/src/components/widgets/RadioWidget.tsx b/packages/core/src/components/widgets/RadioWidget.tsx index 68012e97c6..4bdac401a8 100644 --- a/packages/core/src/components/widgets/RadioWidget.tsx +++ b/packages/core/src/components/widgets/RadioWidget.tsx @@ -1,12 +1,16 @@ import React, { FocusEvent, useCallback } from "react"; -import { WidgetProps } from "@rjsf/utils"; +import { RJSFSchema, StrictRJSFSchema, WidgetProps } from "@rjsf/utils"; /** The `RadioWidget` is a widget for rendering a radio group. * It is typically used with a string property constrained with enum options. * * @param props - The `WidgetProps` for this component */ -function RadioWidget({ +function RadioWidget< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +>({ options, value, required, @@ -17,7 +21,7 @@ function RadioWidget({ onFocus, onChange, id, -}: WidgetProps) { +}: WidgetProps) { // Generating a unique field name to identify this set of radio buttons const name = Math.random().toString(); const { enumOptions, enumDisabled, inline } = options; diff --git a/packages/core/src/components/widgets/RangeWidget.tsx b/packages/core/src/components/widgets/RangeWidget.tsx index 884d12e045..ab9107b0d2 100644 --- a/packages/core/src/components/widgets/RangeWidget.tsx +++ b/packages/core/src/components/widgets/RangeWidget.tsx @@ -1,14 +1,16 @@ import React from "react"; -import { WidgetProps } from "@rjsf/utils"; +import { RJSFSchema, StrictRJSFSchema, WidgetProps } from "@rjsf/utils"; /** The `RangeWidget` component uses the `BaseInputTemplate` changing the type to `range` and wrapping the result * in a div, with the value along side it. * * @param props - The `WidgetProps` for this component */ -export default function RangeWidget( - props: WidgetProps -) { +export default function RangeWidget< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +>(props: WidgetProps) { const { value, registry: { diff --git a/packages/core/src/components/widgets/SelectWidget.tsx b/packages/core/src/components/widgets/SelectWidget.tsx index 3560d7149a..b5fb460a7b 100644 --- a/packages/core/src/components/widgets/SelectWidget.tsx +++ b/packages/core/src/components/widgets/SelectWidget.tsx @@ -1,5 +1,10 @@ import React, { ChangeEvent, FocusEvent, useCallback } from "react"; -import { processSelectValue, WidgetProps } from "@rjsf/utils"; +import { + processSelectValue, + RJSFSchema, + StrictRJSFSchema, + WidgetProps, +} from "@rjsf/utils"; function getValue( event: React.SyntheticEvent, @@ -19,7 +24,11 @@ function getValue( * * @param props - The `WidgetProps` for this component */ -function SelectWidget({ +function SelectWidget< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +>({ schema, id, options, @@ -33,7 +42,7 @@ function SelectWidget({ onBlur, onFocus, placeholder, -}: WidgetProps) { +}: WidgetProps) { const { enumOptions, enumDisabled } = options; const emptyValue = multiple ? [] : ""; diff --git a/packages/core/src/components/widgets/TextWidget.tsx b/packages/core/src/components/widgets/TextWidget.tsx index 6235b3a193..28e5ff86db 100644 --- a/packages/core/src/components/widgets/TextWidget.tsx +++ b/packages/core/src/components/widgets/TextWidget.tsx @@ -1,13 +1,22 @@ import React from "react"; -import { getTemplate, WidgetProps } from "@rjsf/utils"; +import { + getTemplate, + RJSFSchema, + StrictRJSFSchema, + WidgetProps, +} from "@rjsf/utils"; /** The `TextWidget` component uses the `BaseInputTemplate`. * * @param props - The `WidgetProps` for this component */ -export default function TextWidget(props: WidgetProps) { +export default function TextWidget< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +>(props: WidgetProps) { const { options, registry } = props; - const BaseInputTemplate = getTemplate<"BaseInputTemplate", T, F>( + const BaseInputTemplate = getTemplate<"BaseInputTemplate", T, S, F>( "BaseInputTemplate", registry, options diff --git a/packages/core/src/components/widgets/TextareaWidget.tsx b/packages/core/src/components/widgets/TextareaWidget.tsx index b020c602a9..f76ec91663 100644 --- a/packages/core/src/components/widgets/TextareaWidget.tsx +++ b/packages/core/src/components/widgets/TextareaWidget.tsx @@ -1,11 +1,15 @@ import React, { FocusEvent, useCallback } from "react"; -import { WidgetProps } from "@rjsf/utils"; +import { RJSFSchema, StrictRJSFSchema, WidgetProps } from "@rjsf/utils"; /** The `TextareaWidget` is a widget for rendering input fields as textarea. * * @param props - The `WidgetProps` for this component */ -function TextareaWidget({ +function TextareaWidget< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +>({ id, options = {}, placeholder, @@ -17,7 +21,7 @@ function TextareaWidget({ onChange, onBlur, onFocus, -}: WidgetProps) { +}: WidgetProps) { const handleChange = useCallback( ({ target: { value } }: React.ChangeEvent) => onChange(value === "" ? options.emptyValue : value), diff --git a/packages/core/src/components/widgets/URLWidget.tsx b/packages/core/src/components/widgets/URLWidget.tsx index 628b8721f8..d51fadefba 100644 --- a/packages/core/src/components/widgets/URLWidget.tsx +++ b/packages/core/src/components/widgets/URLWidget.tsx @@ -1,13 +1,22 @@ import React from "react"; -import { getTemplate, WidgetProps } from "@rjsf/utils"; +import { + getTemplate, + RJSFSchema, + StrictRJSFSchema, + WidgetProps, +} from "@rjsf/utils"; /** The `URLWidget` component uses the `BaseInputTemplate` changing the type to `url`. * * @param props - The `WidgetProps` for this component */ -export default function URLWidget(props: WidgetProps) { +export default function URLWidget< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +>(props: WidgetProps) { const { options, registry } = props; - const BaseInputTemplate = getTemplate<"BaseInputTemplate", T, F>( + const BaseInputTemplate = getTemplate<"BaseInputTemplate", T, S, F>( "BaseInputTemplate", registry, options diff --git a/packages/core/src/components/widgets/UpDownWidget.tsx b/packages/core/src/components/widgets/UpDownWidget.tsx index f474f51e3d..f136e62361 100644 --- a/packages/core/src/components/widgets/UpDownWidget.tsx +++ b/packages/core/src/components/widgets/UpDownWidget.tsx @@ -1,15 +1,22 @@ import React from "react"; -import { getTemplate, WidgetProps } from "@rjsf/utils"; +import { + getTemplate, + RJSFSchema, + StrictRJSFSchema, + WidgetProps, +} from "@rjsf/utils"; /** The `UpDownWidget` component uses the `BaseInputTemplate` changing the type to `number`. * * @param props - The `WidgetProps` for this component */ -export default function UpDownWidget( - props: WidgetProps -) { +export default function UpDownWidget< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +>(props: WidgetProps) { const { options, registry } = props; - const BaseInputTemplate = getTemplate<"BaseInputTemplate", T, F>( + const BaseInputTemplate = getTemplate<"BaseInputTemplate", T, S, F>( "BaseInputTemplate", registry, options diff --git a/packages/core/src/components/widgets/index.ts b/packages/core/src/components/widgets/index.ts index c95b682936..f3703d73b4 100644 --- a/packages/core/src/components/widgets/index.ts +++ b/packages/core/src/components/widgets/index.ts @@ -1,4 +1,4 @@ -import { RegistryWidgetsType } from "@rjsf/utils"; +import { RegistryWidgetsType, RJSFSchema, StrictRJSFSchema } from "@rjsf/utils"; import AltDateWidget from "./AltDateWidget"; import AltDateTimeWidget from "./AltDateTimeWidget"; @@ -19,25 +19,31 @@ import TextWidget from "./TextWidget"; import URLWidget from "./URLWidget"; import UpDownWidget from "./UpDownWidget"; -const widgets: RegistryWidgetsType = { - PasswordWidget, - RadioWidget, - UpDownWidget, - RangeWidget, - SelectWidget, - TextWidget, - DateWidget, - DateTimeWidget, - AltDateWidget, - AltDateTimeWidget, - EmailWidget, - URLWidget, - TextareaWidget, - HiddenWidget, - ColorWidget, - FileWidget, - CheckboxWidget, - CheckboxesWidget, -}; +function widgets< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +>(): RegistryWidgetsType { + return { + PasswordWidget, + RadioWidget, + UpDownWidget, + RangeWidget, + SelectWidget, + TextWidget, + DateWidget, + DateTimeWidget, + AltDateWidget, + AltDateTimeWidget, + EmailWidget, + URLWidget, + TextareaWidget, + HiddenWidget, + ColorWidget, + FileWidget, + CheckboxWidget, + CheckboxesWidget, + }; +} export default widgets; diff --git a/packages/core/src/getDefaultRegistry.ts b/packages/core/src/getDefaultRegistry.ts index 46c3ee98d7..9ed699e263 100644 --- a/packages/core/src/getDefaultRegistry.ts +++ b/packages/core/src/getDefaultRegistry.ts @@ -1,4 +1,4 @@ -import { Registry } from "@rjsf/utils"; +import { Registry, RJSFSchema, StrictRJSFSchema } from "@rjsf/utils"; import fields from "./components/fields"; import templates from "./components/templates"; @@ -8,15 +8,16 @@ import widgets from "./components/widgets"; * plus an empty `rootSchema` and `formContext. We omit schemaUtils here because it cannot be defaulted without a * rootSchema and validator. It will be added into the computed registry later in the Form. */ -export default function getDefaultRegistry(): Omit< - Registry, - "schemaUtils" -> { +export default function getDefaultRegistry< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +>(): Omit, "schemaUtils"> { return { - fields, - templates, - widgets, - rootSchema: {}, + fields: fields(), + templates: templates(), + widgets: widgets(), + rootSchema: {} as S, formContext: {} as F, }; } diff --git a/packages/core/src/withTheme.tsx b/packages/core/src/withTheme.tsx index 108d5a5934..4a0e500a96 100644 --- a/packages/core/src/withTheme.tsx +++ b/packages/core/src/withTheme.tsx @@ -1,23 +1,30 @@ import React, { ForwardedRef, forwardRef } from "react"; import Form, { FormProps } from "./components/Form"; +import { RJSFSchema, StrictRJSFSchema } from "@rjsf/utils"; /** The properties for the `withTheme` function, essentially a subset of properties from the `FormProps` that can be * overridden while creating a theme */ -export type ThemeProps = Pick< - FormProps, +export type ThemeProps< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +> = Pick< + FormProps, "fields" | "templates" | "widgets" | "_internalFormWrapper" >; /** A Higher-Order component that creates a wrapper around a `Form` with the overrides from the `WithThemeProps` */ -export default function withTheme( - themeProps: ThemeProps -) { +export default function withTheme< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +>(themeProps: ThemeProps) { return forwardRef( ( - { fields, widgets, templates, ...directProps }: FormProps, - ref: ForwardedRef> + { fields, widgets, templates, ...directProps }: FormProps, + ref: ForwardedRef> ) => { fields = { ...themeProps.fields, ...fields }; widgets = { ...themeProps.widgets, ...widgets }; @@ -31,7 +38,7 @@ export default function withTheme( }; return ( - + {...themeProps} {...directProps} fields={fields} diff --git a/packages/fluent-ui/src/WrapIfAdditionalTemplate/WrapIfAdditionalTemplate.tsx b/packages/fluent-ui/src/WrapIfAdditionalTemplate/WrapIfAdditionalTemplate.tsx index 84025df8e0..b7b516a46e 100644 --- a/packages/fluent-ui/src/WrapIfAdditionalTemplate/WrapIfAdditionalTemplate.tsx +++ b/packages/fluent-ui/src/WrapIfAdditionalTemplate/WrapIfAdditionalTemplate.tsx @@ -1,8 +1,8 @@ import React from "react"; import { WrapIfAdditionalTemplateProps } from "@rjsf/utils"; -export default function WrapIfAdditionalTemplate( - props: WrapIfAdditionalTemplateProps +export default function WrapIfAdditionalTemplate( + props: WrapIfAdditionalTemplateProps ) { const { children } = props; // TODO Implement WrapIfAdditionalTemplate features in FluentUI (#2777) diff --git a/packages/utils/src/allowAdditionalItems.ts b/packages/utils/src/allowAdditionalItems.ts index 4b78b126af..855cae2fbf 100644 --- a/packages/utils/src/allowAdditionalItems.ts +++ b/packages/utils/src/allowAdditionalItems.ts @@ -1,5 +1,5 @@ import isObject from "./isObject"; -import { RJSFSchema } from "./types"; +import { RJSFSchema, StrictRJSFSchema } from "./types"; /** Checks the schema to see if it is allowing additional items, by verifying that `schema.additionalItems` is an * object. The user is warned in the console if `schema.additionalItems` has the value `true`. @@ -7,7 +7,9 @@ import { RJSFSchema } from "./types"; * @param schema - The schema object to check * @returns - True if additional items is allowed, otherwise false */ -export default function allowAdditionalItems(schema: RJSFSchema) { +export default function allowAdditionalItems< + S extends StrictRJSFSchema = RJSFSchema +>(schema: S) { if (schema.additionalItems === true) { console.warn("additionalItems=true is currently not supported"); } diff --git a/packages/utils/src/canExpand.ts b/packages/utils/src/canExpand.ts index c50a3119ed..841854ad2b 100644 --- a/packages/utils/src/canExpand.ts +++ b/packages/utils/src/canExpand.ts @@ -1,4 +1,4 @@ -import { RJSFSchema, UiSchema } from "./types"; +import { RJSFSchema, StrictRJSFSchema, UiSchema } from "./types"; import getUiOptions from "./getUiOptions"; /** Checks whether the field described by `schema`, having the `uiSchema` and `formData` supports expanding. The UI for @@ -10,15 +10,15 @@ import getUiOptions from "./getUiOptions"; * @param [formData] - The formData for the field * @returns - True if the schema element has additionalProperties, is expandable, and not at the maxProperties limit */ -export default function canExpand( - schema: RJSFSchema, - uiSchema: UiSchema = {}, - formData?: T -) { +export default function canExpand< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +>(schema: RJSFSchema, uiSchema: UiSchema = {}, formData?: T) { if (!schema.additionalProperties) { return false; } - const { expandable = true } = getUiOptions(uiSchema); + const { expandable = true } = getUiOptions(uiSchema); if (expandable === false) { return expandable; } diff --git a/packages/utils/src/createSchemaUtils.ts b/packages/utils/src/createSchemaUtils.ts index 6e42a31a46..fb71bb01d9 100644 --- a/packages/utils/src/createSchemaUtils.ts +++ b/packages/utils/src/createSchemaUtils.ts @@ -5,6 +5,7 @@ import { PathSchema, RJSFSchema, SchemaUtilsType, + StrictRJSFSchema, UiSchema, ValidationData, ValidatorType, @@ -27,16 +28,18 @@ import { * and `rootSchema` generally does not change across a `Form`, this allows for providing a simplified set of APIs to the * `@rjsf/core` components and the various themes as well. This class implements the `SchemaUtilsType` interface. */ -class SchemaUtils implements SchemaUtilsType { - rootSchema: RJSFSchema; - validator: ValidatorType; +class SchemaUtils + implements SchemaUtilsType +{ + rootSchema: S; + validator: ValidatorType; /** Constructs the `SchemaUtils` instance with the given `validator` and `rootSchema` stored as instance variables * * @param validator - An implementation of the `ValidatorType` interface that will be forwarded to all the APIs * @param rootSchema - The root schema that will be forwarded to all the APIs */ - constructor(validator: ValidatorType, rootSchema: RJSFSchema) { + constructor(validator: ValidatorType, rootSchema: S) { this.rootSchema = rootSchema; this.validator = validator; } @@ -58,8 +61,8 @@ class SchemaUtils implements SchemaUtilsType { * @returns - True if the `SchemaUtilsType` differs from the given `validator` or `rootSchema` */ doesSchemaUtilsDiffer( - validator: ValidatorType, - rootSchema: RJSFSchema + validator: ValidatorType, + rootSchema: S ): boolean { if (!validator || !rootSchema) { return false; @@ -78,11 +81,11 @@ class SchemaUtils implements SchemaUtilsType { * @returns - The resulting `formData` with all the defaults provided */ getDefaultFormState( - schema: RJSFSchema, + schema: S, formData?: T, includeUndefinedValues = false ): T | T[] | undefined { - return getDefaultFormState( + return getDefaultFormState( this.validator, schema, formData, @@ -98,8 +101,8 @@ class SchemaUtils implements SchemaUtilsType { * @param [uiSchema] - The UI schema from which to derive potentially displayable information * @returns - True if the label should be displayed or false if it should not */ - getDisplayLabel(schema: RJSFSchema, uiSchema?: UiSchema) { - return getDisplayLabel( + getDisplayLabel(schema: S, uiSchema?: UiSchema) { + return getDisplayLabel( this.validator, schema, uiSchema, @@ -113,8 +116,8 @@ class SchemaUtils implements SchemaUtilsType { * @param options - The list of options to find a matching options from * @returns - The index of the matched option or 0 if none is available */ - getMatchingOption(formData: T, options: RJSFSchema[]) { - return getMatchingOption( + getMatchingOption(formData: T, options: S[]) { + return getMatchingOption( this.validator, formData, options, @@ -128,8 +131,8 @@ class SchemaUtils implements SchemaUtilsType { * @param [uiSchema] - The UI schema from which to check the widget * @returns - True if schema/uiSchema contains an array of files, otherwise false */ - isFilesArray(schema: RJSFSchema, uiSchema?: UiSchema) { - return isFilesArray( + isFilesArray(schema: S, uiSchema?: UiSchema) { + return isFilesArray( this.validator, schema, uiSchema, @@ -142,8 +145,8 @@ class SchemaUtils implements SchemaUtilsType { * @param schema - The schema for which check for a multi-select flag is desired * @returns - True if schema contains a multi-select, otherwise false */ - isMultiSelect(schema: RJSFSchema) { - return isMultiSelect(this.validator, schema, this.rootSchema); + isMultiSelect(schema: S) { + return isMultiSelect(this.validator, schema, this.rootSchema); } /** Checks to see if the `schema` combination represents a select @@ -151,8 +154,8 @@ class SchemaUtils implements SchemaUtilsType { * @param schema - The schema for which check for a select flag is desired * @returns - True if schema contains a select, otherwise false */ - isSelect(schema: RJSFSchema) { - return isSelect(this.validator, schema, this.rootSchema); + isSelect(schema: S) { + return isSelect(this.validator, schema, this.rootSchema); } /** Merges the errors in `additionalErrorSchema` into the existing `validationData` by combining the hierarchies in @@ -168,7 +171,7 @@ class SchemaUtils implements SchemaUtilsType { validationData: ValidationData, additionalErrorSchema?: ErrorSchema ): ValidationData { - return mergeValidationData( + return mergeValidationData( this.validator, validationData, additionalErrorSchema @@ -183,8 +186,8 @@ class SchemaUtils implements SchemaUtilsType { * @param [rawFormData] - The current formData, if any, to assist retrieving a schema * @returns - The schema having its conditions, additional properties, references and dependencies resolved */ - retrieveSchema(schema: RJSFSchema, rawFormData: T) { - return retrieveSchema( + retrieveSchema(schema: S, rawFormData: T) { + return retrieveSchema( this.validator, schema, this.rootSchema, @@ -202,13 +205,13 @@ class SchemaUtils implements SchemaUtilsType { * @returns - The `IdSchema` object for the `schema` */ toIdSchema( - schema: RJSFSchema, + schema: S, id?: string | null, formData?: T, idPrefix = "root", idSeparator = "_" ): IdSchema { - return toIdSchema( + return toIdSchema( this.validator, schema, id, @@ -226,8 +229,8 @@ class SchemaUtils implements SchemaUtilsType { * @param [formData] - The current formData, if any, onto which to provide any missing defaults * @returns - The `PathSchema` object for the `schema` */ - toPathSchema(schema: RJSFSchema, name?: string, formData?: T): PathSchema { - return toPathSchema( + toPathSchema(schema: S, name?: string, formData?: T): PathSchema { + return toPathSchema( this.validator, schema, name, @@ -244,9 +247,10 @@ class SchemaUtils implements SchemaUtilsType { * @param rootSchema - The root schema that will be forwarded to all the APIs * @returns - An implementation of a `SchemaUtilsType` interface */ -export default function createSchemaUtils( - validator: ValidatorType, - rootSchema: RJSFSchema -): SchemaUtilsType { - return new SchemaUtils(validator, rootSchema); +export default function createSchemaUtils< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +>(validator: ValidatorType, rootSchema: S): SchemaUtilsType { + return new SchemaUtils(validator, rootSchema); } diff --git a/packages/utils/src/findSchemaDefinition.ts b/packages/utils/src/findSchemaDefinition.ts index 88609d2d5d..c6d6ffa877 100644 --- a/packages/utils/src/findSchemaDefinition.ts +++ b/packages/utils/src/findSchemaDefinition.ts @@ -2,7 +2,7 @@ import jsonpointer from "jsonpointer"; import omit from "lodash/omit"; import { REF_KEY } from "./constants"; -import { GenericObjectType, RJSFSchema } from "./types"; +import { GenericObjectType, RJSFSchema, StrictRJSFSchema } from "./types"; /** Splits out the value at the `key` in `object` from the `object`, returning an array that contains in the first * location, the `object` minus the `key: value` and in the second location the `value`. @@ -30,10 +30,9 @@ export function splitKeyElementFromObject( * @returns - The sub-schema within the `rootSchema` which matches the `$ref` if it exists * @throws - Error indicating that no schema for that reference exists */ -export default function findSchemaDefinition( - $ref?: string, - rootSchema: RJSFSchema = {} -): RJSFSchema { +export default function findSchemaDefinition< + S extends StrictRJSFSchema = RJSFSchema +>($ref?: string, rootSchema: S = {} as S): S { let ref = $ref || ""; if (ref.startsWith("#")) { // Decode URI fragment representation. @@ -41,13 +40,13 @@ export default function findSchemaDefinition( } else { throw new Error(`Could not find a definition for ${$ref}.`); } - const current: RJSFSchema = jsonpointer.get(rootSchema, ref); + const current: S = jsonpointer.get(rootSchema, ref); if (current === undefined) { throw new Error(`Could not find a definition for ${$ref}.`); } if (current[REF_KEY]) { const [remaining, theRef] = splitKeyElementFromObject(REF_KEY, current); - const subSchema = findSchemaDefinition(theRef, rootSchema); + const subSchema = findSchemaDefinition(theRef, rootSchema); if (Object.keys(remaining).length > 0) { return { ...remaining, ...subSchema }; } diff --git a/packages/utils/src/getInputProps.ts b/packages/utils/src/getInputProps.ts index 209ae7b198..847807ea81 100644 --- a/packages/utils/src/getInputProps.ts +++ b/packages/utils/src/getInputProps.ts @@ -1,5 +1,10 @@ import rangeSpec from "./rangeSpec"; -import { InputPropsType, RJSFSchema, UIOptionsType } from "./types"; +import { + InputPropsType, + RJSFSchema, + StrictRJSFSchema, + UIOptionsType, +} from "./types"; /** Using the `schema`, `defaultType` and `options`, extract out the props for the element that make sense. * @@ -9,10 +14,14 @@ import { InputPropsType, RJSFSchema, UIOptionsType } from "./types"; * @param [autoDefaultStepAny=true] - Determines whether to auto-default step=any when the type is number and no step * @returns - The extracted `InputPropsType` object */ -export default function getInputProps( +export default function getInputProps< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +>( schema: RJSFSchema, defaultType?: string, - options: UIOptionsType = {}, + options: UIOptionsType = {}, autoDefaultStepAny = true ): InputPropsType { const inputProps: InputPropsType = { diff --git a/packages/utils/src/getSchemaType.ts b/packages/utils/src/getSchemaType.ts index 3fbef38e99..dcd5557c3c 100644 --- a/packages/utils/src/getSchemaType.ts +++ b/packages/utils/src/getSchemaType.ts @@ -1,5 +1,5 @@ import guessType from "./guessType"; -import { RJSFSchema } from "./types"; +import { RJSFSchema, StrictRJSFSchema } from "./types"; /** Gets the type of a given `schema`. If the type is not explicitly defined, then an attempt is made to infer it from * other elements of the schema as follows: @@ -12,8 +12,8 @@ import { RJSFSchema } from "./types"; * @param schema - The schema for which to get the type * @returns - The type of the schema */ -export default function getSchemaType( - schema: RJSFSchema +export default function getSchemaType( + schema: S ): string | string[] | undefined { let { type } = schema; diff --git a/packages/utils/src/getSubmitButtonOptions.ts b/packages/utils/src/getSubmitButtonOptions.ts index 31d752745e..451abbcc48 100644 --- a/packages/utils/src/getSubmitButtonOptions.ts +++ b/packages/utils/src/getSubmitButtonOptions.ts @@ -1,6 +1,11 @@ import { SUBMIT_BTN_OPTIONS_KEY } from "./constants"; import getUiOptions from "./getUiOptions"; -import { UiSchema, UISchemaSubmitButtonOptions } from "./types"; +import { + RJSFSchema, + StrictRJSFSchema, + UiSchema, + UISchemaSubmitButtonOptions, +} from "./types"; /** The default submit button options, exported for testing purposes */ @@ -17,10 +22,12 @@ export const DEFAULT_OPTIONS: UISchemaSubmitButtonOptions = { * @param [uiSchema={}] - the UI Schema from which to extract submit button props * @returns - The merging of the `DEFAULT_OPTIONS` with any custom ones */ -export default function getSubmitButtonOptions( - uiSchema: UiSchema = {} -): UISchemaSubmitButtonOptions { - const uiOptions = getUiOptions(uiSchema); +export default function getSubmitButtonOptions< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +>(uiSchema: UiSchema = {}): UISchemaSubmitButtonOptions { + const uiOptions = getUiOptions(uiSchema); if (uiOptions && uiOptions[SUBMIT_BTN_OPTIONS_KEY]) { const options = uiOptions[ SUBMIT_BTN_OPTIONS_KEY diff --git a/packages/utils/src/getTemplate.ts b/packages/utils/src/getTemplate.ts index 4ba0de7567..a7ec9ed8f1 100644 --- a/packages/utils/src/getTemplate.ts +++ b/packages/utils/src/getTemplate.ts @@ -1,4 +1,10 @@ -import { TemplatesType, Registry, UIOptionsType } from "./types"; +import { + TemplatesType, + Registry, + UIOptionsType, + StrictRJSFSchema, + RJSFSchema, +} from "./types"; /** Returns the template with the given `name` from either the `uiSchema` if it is defined or from the `registry` * otherwise. NOTE, since `ButtonTemplates` are not overridden in `uiSchema` only those in the `registry` are returned. @@ -9,14 +15,15 @@ import { TemplatesType, Registry, UIOptionsType } from "./types"; * @returns - The template from either the `uiSchema` or `registry` for the `name` */ export default function getTemplate< - Name extends keyof TemplatesType, + Name extends keyof TemplatesType, T = any, + S extends StrictRJSFSchema = RJSFSchema, F = any >( name: Name, - registry: Registry, - uiOptions: UIOptionsType = {} -): TemplatesType[Name] { + registry: Registry, + uiOptions: UIOptionsType = {} +): TemplatesType[Name] { const { templates } = registry; if (name === "ButtonTemplates") { return templates[name]; @@ -24,6 +31,7 @@ export default function getTemplate< return ( // Evaluating uiOptions[name] results in TS2590: Expression produces a union type that is too complex to represent // To avoid that, we cast uiOptions to `any` before accessing the name field - ((uiOptions as any)[name] as TemplatesType[Name]) || templates[name] + ((uiOptions as any)[name] as TemplatesType[Name]) || + templates[name] ); } diff --git a/packages/utils/src/getUiOptions.ts b/packages/utils/src/getUiOptions.ts index 65065b549d..d7655b4cb1 100644 --- a/packages/utils/src/getUiOptions.ts +++ b/packages/utils/src/getUiOptions.ts @@ -1,16 +1,18 @@ import { UI_OPTIONS_KEY, UI_WIDGET_KEY } from "./constants"; import isObject from "./isObject"; -import { UIOptionsType, UiSchema } from "./types"; +import { RJSFSchema, StrictRJSFSchema, UIOptionsType, UiSchema } from "./types"; /** Get all passed options from ui:options, and ui:, returning them in an object with the `ui:` * stripped off. * * @param [uiSchema={}] - The UI Schema from which to get any `ui:xxx` options - * @returns - An object containing all of the `ui:xxx` options with the stripped off + * @returns - An object containing all the `ui:xxx` options with the stripped off */ -export default function getUiOptions( - uiSchema: UiSchema = {} -): UIOptionsType { +export default function getUiOptions< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +>(uiSchema: UiSchema = {}): UIOptionsType { return Object.keys(uiSchema) .filter((key) => key.indexOf("ui:") === 0) .reduce((options, key) => { diff --git a/packages/utils/src/getWidget.tsx b/packages/utils/src/getWidget.tsx index 6c212225cb..f825b64aa6 100644 --- a/packages/utils/src/getWidget.tsx +++ b/packages/utils/src/getWidget.tsx @@ -3,7 +3,12 @@ import ReactIs from "react-is"; import get from "lodash/get"; import set from "lodash/set"; -import { RJSFSchema, Widget, RegistryWidgetsType } from "./types"; +import { + RJSFSchema, + Widget, + RegistryWidgetsType, + StrictRJSFSchema, +} from "./types"; import getSchemaType from "./getSchemaType"; /** The map of schema types to widget type to widget name @@ -67,8 +72,12 @@ const widgetMap: { [k: string]: { [j: string]: string } } = { * @param AWidget - A widget that will be wrapped or one that is already wrapped * @returns - The wrapper widget */ -function mergeWidgetOptions(AWidget: Widget) { - let MergedWidget: Widget = get(AWidget, "MergedWidget"); +function mergeWidgetOptions< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +>(AWidget: Widget) { + let MergedWidget: Widget = get(AWidget, "MergedWidget"); // cache return value as property of widget for proper react reconciliation if (!MergedWidget) { const defaultOptions = @@ -92,11 +101,15 @@ function mergeWidgetOptions(AWidget: Widget) { * @returns - The `Widget` component to use * @throws - An error if there is no `Widget` component that can be returned */ -export default function getWidget( +export default function getWidget< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +>( schema: RJSFSchema, - widget?: Widget | string, - registeredWidgets: RegistryWidgetsType = {} -): Widget { + widget?: Widget | string, + registeredWidgets: RegistryWidgetsType = {} +): Widget { const type = getSchemaType(schema); if ( @@ -104,7 +117,7 @@ export default function getWidget( (widget && ReactIs.isForwardRef(React.createElement(widget))) || ReactIs.isMemo(widget) ) { - return mergeWidgetOptions(widget as Widget); + return mergeWidgetOptions(widget as Widget); } if (typeof widget !== "string") { @@ -113,7 +126,7 @@ export default function getWidget( if (widget in registeredWidgets) { const registeredWidget = registeredWidgets[widget]; - return getWidget(schema, registeredWidget, registeredWidgets); + return getWidget(schema, registeredWidget, registeredWidgets); } if (typeof type === "string") { @@ -123,7 +136,7 @@ export default function getWidget( if (widget in widgetMap[type]) { const registeredWidget = registeredWidgets[widgetMap[type][widget]]; - return getWidget(schema, registeredWidget, registeredWidgets); + return getWidget(schema, registeredWidget, registeredWidgets); } } diff --git a/packages/utils/src/hasWidget.ts b/packages/utils/src/hasWidget.ts index 117340a7d4..2e5816fbce 100644 --- a/packages/utils/src/hasWidget.ts +++ b/packages/utils/src/hasWidget.ts @@ -1,5 +1,10 @@ import getWidget from "./getWidget"; -import { RegistryWidgetsType, RJSFSchema, Widget } from "./types"; +import { + RegistryWidgetsType, + RJSFSchema, + StrictRJSFSchema, + Widget, +} from "./types"; /** Detects whether the `widget` exists for the `schema` with the associated `registryWidgets` and returns true if it * does, or false if it doesn't. @@ -9,10 +14,14 @@ import { RegistryWidgetsType, RJSFSchema, Widget } from "./types"; * @param [registeredWidgets={}] - A registry of widget name to `Widget` implementation * @returns - True if the widget exists, false otherwise */ -export default function hasWidget( +export default function hasWidget< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +>( schema: RJSFSchema, - widget: Widget | string, - registeredWidgets: RegistryWidgetsType = {} + widget: Widget | string, + registeredWidgets: RegistryWidgetsType = {} ) { try { getWidget(schema, widget, registeredWidgets); diff --git a/packages/utils/src/isConstant.ts b/packages/utils/src/isConstant.ts index fa1222c249..24d4a2a43c 100644 --- a/packages/utils/src/isConstant.ts +++ b/packages/utils/src/isConstant.ts @@ -1,5 +1,5 @@ import { CONST_KEY } from "./constants"; -import { RJSFSchema } from "./types"; +import { RJSFSchema, StrictRJSFSchema } from "./types"; /** This function checks if the given `schema` matches a single constant value. This happens when either the schema has * an `enum` array with a single value or there is a `const` defined. @@ -7,7 +7,9 @@ import { RJSFSchema } from "./types"; * @param schema - The schema for a field * @returns - True if the `schema` has a single constant value, false otherwise */ -export default function isConstant(schema: RJSFSchema) { +export default function isConstant( + schema: S +) { return ( (Array.isArray(schema.enum) && schema.enum.length === 1) || CONST_KEY in schema diff --git a/packages/utils/src/isCustomWidget.ts b/packages/utils/src/isCustomWidget.ts index 6ef21ac37b..d3c291c74c 100644 --- a/packages/utils/src/isCustomWidget.ts +++ b/packages/utils/src/isCustomWidget.ts @@ -1,18 +1,20 @@ import getUiOptions from "./getUiOptions"; -import { UiSchema } from "./types"; +import { RJSFSchema, StrictRJSFSchema, UiSchema } from "./types"; /** Checks to see if the `uiSchema` contains the `widget` field and that the widget is not `hidden` * * @param uiSchema - The UI Schema from which to detect if it is customized * @returns - True if the `uiSchema` describes a custom widget, false otherwise */ -export default function isCustomWidget( - uiSchema: UiSchema = {} -) { +export default function isCustomWidget< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +>(uiSchema: UiSchema = {}) { return ( // TODO: Remove the `&& uiSchema['ui:widget'] !== 'hidden'` once we support hidden widgets for arrays. // https://react-jsonschema-form.readthedocs.io/en/latest/usage/widgets/#hidden-widgets - "widget" in getUiOptions(uiSchema) && - getUiOptions(uiSchema)["widget"] !== "hidden" + "widget" in getUiOptions(uiSchema) && + getUiOptions(uiSchema)["widget"] !== "hidden" ); } diff --git a/packages/utils/src/isFixedItems.ts b/packages/utils/src/isFixedItems.ts index 4ea2bd549f..f28c4715b3 100644 --- a/packages/utils/src/isFixedItems.ts +++ b/packages/utils/src/isFixedItems.ts @@ -1,5 +1,5 @@ import isObject from "./isObject"; -import { RJSFSchema } from "./types"; +import { RJSFSchema, StrictRJSFSchema } from "./types"; /** Detects whether the given `schema` contains fixed items. This is the case when `schema.items` is a non-empty array * that only contains objects. @@ -7,7 +7,9 @@ import { RJSFSchema } from "./types"; * @param schema - The schema in which to check for fixed items * @returns - True if there are fixed items in the schema, false otherwise */ -export default function isFixedItems(schema: RJSFSchema) { +export default function isFixedItems( + schema: S +) { return ( Array.isArray(schema.items) && schema.items.length > 0 && diff --git a/packages/utils/src/mergeDefaultsWithFormData.ts b/packages/utils/src/mergeDefaultsWithFormData.ts index 6591a9afa5..98c90c17eb 100644 --- a/packages/utils/src/mergeDefaultsWithFormData.ts +++ b/packages/utils/src/mergeDefaultsWithFormData.ts @@ -1,6 +1,7 @@ import get from "lodash/get"; import isObject from "./isObject"; +import { GenericObjectType } from "../src"; /** Merges the `defaults` object of type `T` into the `formData` of type `T` * @@ -31,9 +32,8 @@ export default function mergeDefaultsWithFormData( return mapped as unknown as T; } if (isObject(formData)) { - // eslint-disable-next-line no-unused-vars const acc: { [key in keyof T]: any } = Object.assign({}, defaults); // Prevent mutation of source object. - return Object.keys(formData).reduce((acc, key) => { + return Object.keys(formData as GenericObjectType).reduce((acc, key) => { acc[key as keyof T] = mergeDefaultsWithFormData( defaults ? get(defaults, key) : {}, get(formData, key) diff --git a/packages/utils/src/optionsList.ts b/packages/utils/src/optionsList.ts index fb5f92649b..fa17c46fa5 100644 --- a/packages/utils/src/optionsList.ts +++ b/packages/utils/src/optionsList.ts @@ -1,5 +1,5 @@ import toConstant from "./toConstant"; -import { RJSFSchema, EnumOptionsType } from "./types"; +import { RJSFSchema, EnumOptionsType, StrictRJSFSchema } from "./types"; /** Gets the list of options from the schema. If the schema has an enum list, then those enum values are returned. The * labels for the options will be extracted from the non-standard, RJSF-deprecated `enumNames` if it exists, otherwise @@ -9,12 +9,12 @@ import { RJSFSchema, EnumOptionsType } from "./types"; * @param schema - The schema from which to extract the options list * @returns - The list of options from the schema */ -export default function optionsList( - schema: RJSFSchema -): EnumOptionsType[] | undefined { +export default function optionsList( + schema: S +): EnumOptionsType[] | undefined { // enumNames was deprecated in v5 and is intentionally omitted from the RJSFSchema type. // Cast the type to include enumNames so the feature still works. - const schemaWithEnumNames = schema as RJSFSchema & { enumNames?: string[] }; + const schemaWithEnumNames = schema as S & { enumNames?: string[] }; if (schemaWithEnumNames.enumNames && process.env.NODE_ENV !== "production") { console.warn( "The enumNames property is deprecated and may be removed in a future major release." @@ -32,7 +32,7 @@ export default function optionsList( return ( altSchemas && altSchemas.map((aSchemaDef) => { - const aSchema = aSchemaDef as RJSFSchema; + const aSchema = aSchemaDef as S; const value = toConstant(aSchema); const label = aSchema.title || String(value); return { diff --git a/packages/utils/src/processSelectValue.ts b/packages/utils/src/processSelectValue.ts index a4690ddc10..522ac4226e 100644 --- a/packages/utils/src/processSelectValue.ts +++ b/packages/utils/src/processSelectValue.ts @@ -1,6 +1,6 @@ import get from "lodash/get"; -import { RJSFSchema, UIOptionsType } from "./types"; +import { RJSFSchema, StrictRJSFSchema, UIOptionsType } from "./types"; import asNumber from "./asNumber"; import guessType from "./guessType"; @@ -15,11 +15,11 @@ const nums = new Set(["number", "integer"]); * @param [options] - The UIOptionsType from which to potentially extract the emptyValue * @returns - The `value` converted to the proper type */ -export default function processSelectValue( - schema: RJSFSchema, - value?: any, - options?: UIOptionsType -) { +export default function processSelectValue< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +>(schema: S, value?: any, options?: UIOptionsType) { const { enum: schemaEnum, type, items } = schema; if (value === "") { return options && options.emptyValue !== undefined diff --git a/packages/utils/src/rangeSpec.ts b/packages/utils/src/rangeSpec.ts index 0d3d1575fb..bfddbbc34c 100644 --- a/packages/utils/src/rangeSpec.ts +++ b/packages/utils/src/rangeSpec.ts @@ -1,4 +1,4 @@ -import { RangeSpecType } from "./types"; +import { RangeSpecType, StrictRJSFSchema } from "./types"; import { RJSFSchema } from "./types"; /** Extracts the range spec information `{ step?: number, min?: number, max?: number }` that can be spread onto an HTML @@ -7,7 +7,9 @@ import { RJSFSchema } from "./types"; * @param schema - The schema from which to extract the range spec * @returns - A range specification from the schema */ -export default function rangeSpec(schema: RJSFSchema) { +export default function rangeSpec( + schema: S +) { const spec: RangeSpecType = {}; if (schema.multipleOf) { spec.step = schema.multipleOf; diff --git a/packages/utils/src/schema/getDefaultFormState.ts b/packages/utils/src/schema/getDefaultFormState.ts index 8dae266b89..8208e445bf 100644 --- a/packages/utils/src/schema/getDefaultFormState.ts +++ b/packages/utils/src/schema/getDefaultFormState.ts @@ -16,7 +16,12 @@ import isObject from "../isObject"; import isFixedItems from "../isFixedItems"; import mergeDefaultsWithFormData from "../mergeDefaultsWithFormData"; import mergeObjects from "../mergeObjects"; -import { GenericObjectType, RJSFSchema, ValidatorType } from "../types"; +import { + GenericObjectType, + RJSFSchema, + StrictRJSFSchema, + ValidatorType, +} from "../types"; import isMultiSelect from "./isMultiSelect"; import retrieveSchema, { resolveDependencies } from "./retrieveSchema"; @@ -43,16 +48,18 @@ export enum AdditionalItemsHandling { * @param [idx=-1] - Index, if non-negative, will be used to return the idx-th element in a `schema.items` array * @returns - The best fit schema object from the `schema` given the `additionalItems` and `idx` modifiers */ -export function getInnerSchemaForArrayItem( - schema: RJSFSchema, +export function getInnerSchemaForArrayItem< + S extends StrictRJSFSchema = RJSFSchema +>( + schema: S, additionalItems: AdditionalItemsHandling = AdditionalItemsHandling.Ignore, idx = -1 -): RJSFSchema { +): S { if (idx >= 0) { if (Array.isArray(schema.items) && idx < schema.items.length) { const item = schema.items[idx]; if (typeof item !== "boolean") { - return item; + return item as S; } } } else if ( @@ -60,15 +67,15 @@ export function getInnerSchemaForArrayItem( !Array.isArray(schema.items) && typeof schema.items !== "boolean" ) { - return schema.items; + return schema.items as S; } if ( additionalItems !== AdditionalItemsHandling.Ignore && isObject(schema.additionalItems) ) { - return schema.additionalItems as RJSFSchema; + return schema.additionalItems as S; } - return {}; + return {} as S; } /** Computes the defaults for the current `schema` given the `rawFormData` and `parentDefaults` if any. This drills into @@ -82,11 +89,14 @@ export function getInnerSchemaForArrayItem( * @param [includeUndefinedValues=false] - Optional flag, if true, cause undefined values to be added as defaults * @returns - The resulting `formData` with all the defaults provided */ -export function computeDefaults( - validator: ValidatorType, - schema: RJSFSchema, +export function computeDefaults< + T = any, + S extends StrictRJSFSchema = RJSFSchema +>( + validator: ValidatorType, + schema: S, parentDefaults?: T, - rootSchema: RJSFSchema = {}, + rootSchema: S = {} as S, rawFormData?: T, includeUndefinedValues = false ): T | T[] | undefined { @@ -104,8 +114,8 @@ export function computeDefaults( defaults = schema.default as unknown as T; } else if (REF_KEY in schema) { // Use referenced schema defaults for this node. - const refSchema = findSchemaDefinition(schema[REF_KEY]!, rootSchema); - return computeDefaults( + const refSchema = findSchemaDefinition(schema[REF_KEY]!, rootSchema); + return computeDefaults( validator, refSchema, defaults, @@ -120,7 +130,7 @@ export function computeDefaults( rootSchema, formData ); - return computeDefaults( + return computeDefaults( validator, resolvedSchema, defaults, @@ -129,35 +139,34 @@ export function computeDefaults( includeUndefinedValues ); } else if (isFixedItems(schema)) { - defaults = (schema.items! as RJSFSchema[]).map( - (itemSchema: RJSFSchema, idx: number) => - computeDefaults( - validator, - itemSchema, - Array.isArray(parentDefaults) ? parentDefaults[idx] : undefined, - rootSchema, - formData as T, - includeUndefinedValues - ) + defaults = (schema.items! as S[]).map((itemSchema: S, idx: number) => + computeDefaults( + validator, + itemSchema, + Array.isArray(parentDefaults) ? parentDefaults[idx] : undefined, + rootSchema, + formData as T, + includeUndefinedValues + ) ) as T[]; } else if (ONE_OF_KEY in schema) { schema = schema.oneOf![ getMatchingOption( validator, isEmpty(formData) ? undefined : formData, - schema.oneOf as RJSFSchema[], + schema.oneOf as S[], rootSchema ) - ] as RJSFSchema; + ] as S; } else if (ANY_OF_KEY in schema) { schema = schema.anyOf![ getMatchingOption( validator, isEmpty(formData) ? undefined : formData, - schema.anyOf as RJSFSchema[], + schema.anyOf as S[], rootSchema ) - ] as RJSFSchema; + ] as S; } // Not defaults defined for this node, fallback to generic typed ones. @@ -172,7 +181,7 @@ export function computeDefaults( (acc: GenericObjectType, key: string) => { // Compute the defaults for this node, with the parent defaults we might // have from a previous run: defaults[key]. - const computedDefault = computeDefaults( + const computedDefault = computeDefaults( validator, get(schema, [PROPERTIES_KEY, key]), get(defaults, [key]), @@ -192,20 +201,20 @@ export function computeDefaults( // Inject defaults into existing array defaults if (Array.isArray(defaults)) { defaults = defaults.map((item, idx) => { - const schemaItem: RJSFSchema = getInnerSchemaForArrayItem( + const schemaItem: S = getInnerSchemaForArrayItem( schema, AdditionalItemsHandling.Fallback, idx ); - return computeDefaults(validator, schemaItem, item, rootSchema); + return computeDefaults(validator, schemaItem, item, rootSchema); }) as T[]; } // Deeply inject defaults into already existing form data if (Array.isArray(rawFormData)) { - const schemaItem: RJSFSchema = getInnerSchemaForArrayItem(schema); + const schemaItem: S = getInnerSchemaForArrayItem(schema); defaults = rawFormData.map((item: T, idx: number) => { - return computeDefaults( + return computeDefaults( validator, schemaItem, get(defaults, [idx]), @@ -220,7 +229,7 @@ export function computeDefaults( if (schema.minItems > defaultsLength) { const defaultEntries: T[] = (defaults || []) as T[]; // populate the array with the defaults - const fillerSchema: RJSFSchema = getInnerSchemaForArrayItem( + const fillerSchema: S = getInnerSchemaForArrayItem( schema, AdditionalItemsHandling.Invert ); @@ -255,18 +264,26 @@ export function computeDefaults( * @param [includeUndefinedValues=false] - Optional flag, if true, cause undefined values to be added as defaults * @returns - The resulting `formData` with all the defaults provided */ -export default function getDefaultFormState( - validator: ValidatorType, - theSchema: RJSFSchema, +export default function getDefaultFormState< + T = any, + S extends StrictRJSFSchema = RJSFSchema +>( + validator: ValidatorType, + theSchema: S, formData?: T, - rootSchema?: RJSFSchema, + rootSchema?: S, includeUndefinedValues = false ) { if (!isObject(theSchema)) { throw new Error("Invalid schema: " + theSchema); } - const schema = retrieveSchema(validator, theSchema, rootSchema, formData); - const defaults = computeDefaults( + const schema = retrieveSchema( + validator, + theSchema, + rootSchema, + formData + ); + const defaults = computeDefaults( validator, schema, undefined, diff --git a/packages/utils/src/schema/getDisplayLabel.ts b/packages/utils/src/schema/getDisplayLabel.ts index 74088f9869..856565ab4b 100644 --- a/packages/utils/src/schema/getDisplayLabel.ts +++ b/packages/utils/src/schema/getDisplayLabel.ts @@ -2,7 +2,12 @@ import { UI_FIELD_KEY, UI_WIDGET_KEY } from "../constants"; import getSchemaType from "../getSchemaType"; import getUiOptions from "../getUiOptions"; import isCustomWidget from "../isCustomWidget"; -import { RJSFSchema, UiSchema, ValidatorType } from "../types"; +import { + RJSFSchema, + StrictRJSFSchema, + UiSchema, + ValidatorType, +} from "../types"; import isFilesArray from "./isFilesArray"; import isMultiSelect from "./isMultiSelect"; @@ -15,21 +20,25 @@ import isMultiSelect from "./isMultiSelect"; * @param [rootSchema] - The root schema, used to primarily to look up `$ref`s * @returns - True if the label should be displayed or false if it should not */ -export default function getDisplayLabel( - validator: ValidatorType, - schema: RJSFSchema, - uiSchema: UiSchema = {}, - rootSchema?: RJSFSchema +export default function getDisplayLabel< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +>( + validator: ValidatorType, + schema: S, + uiSchema: UiSchema = {}, + rootSchema?: S ): boolean { - const uiOptions = getUiOptions(uiSchema); + const uiOptions = getUiOptions(uiSchema); const { label = true } = uiOptions; let displayLabel = !!label; const schemaType = getSchemaType(schema); if (schemaType === "array") { displayLabel = - isMultiSelect(validator, schema, rootSchema) || - isFilesArray(validator, schema, uiSchema, rootSchema) || + isMultiSelect(validator, schema, rootSchema) || + isFilesArray(validator, schema, uiSchema, rootSchema) || isCustomWidget(uiSchema); } diff --git a/packages/utils/src/schema/getMatchingOption.ts b/packages/utils/src/schema/getMatchingOption.ts index 42a1fbb3fc..0c621bb2a3 100644 --- a/packages/utils/src/schema/getMatchingOption.ts +++ b/packages/utils/src/schema/getMatchingOption.ts @@ -1,4 +1,4 @@ -import { RJSFSchema, ValidatorType } from "../types"; +import { RJSFSchema, StrictRJSFSchema, ValidatorType } from "../types"; /** Given the `formData` and list of `options`, attempts to find the index of the option that best matches the data. * @@ -8,11 +8,14 @@ import { RJSFSchema, ValidatorType } from "../types"; * @param rootSchema - The root schema, used to primarily to look up `$ref`s * @returns - The index of the matched option or 0 if none is available */ -export default function getMatchingOption( - validator: ValidatorType, +export default function getMatchingOption< + T = any, + S extends StrictRJSFSchema = RJSFSchema +>( + validator: ValidatorType, formData: T | undefined, - options: RJSFSchema[], - rootSchema: RJSFSchema + options: S[], + rootSchema: S ): number { // For performance, skip validating subschemas if formData is undefined. We just // want to get the first option in that case. diff --git a/packages/utils/src/schema/isFilesArray.ts b/packages/utils/src/schema/isFilesArray.ts index b26cf6cd9b..079728dbe8 100644 --- a/packages/utils/src/schema/isFilesArray.ts +++ b/packages/utils/src/schema/isFilesArray.ts @@ -1,5 +1,10 @@ import { UI_WIDGET_KEY } from "../constants"; -import { RJSFSchema, UiSchema, ValidatorType } from "../types"; +import { + RJSFSchema, + StrictRJSFSchema, + UiSchema, + ValidatorType, +} from "../types"; import retrieveSchema from "./retrieveSchema"; /** Checks to see if the `schema` and `uiSchema` combination represents an array of files @@ -10,19 +15,23 @@ import retrieveSchema from "./retrieveSchema"; * @param [rootSchema] - The root schema, used to primarily to look up `$ref`s * @returns - True if schema/uiSchema contains an array of files, otherwise false */ -export default function isFilesArray( - validator: ValidatorType, - schema: RJSFSchema, - uiSchema: UiSchema = {}, - rootSchema?: RJSFSchema +export default function isFilesArray< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +>( + validator: ValidatorType, + schema: S, + uiSchema: UiSchema = {}, + rootSchema?: S ) { if (uiSchema[UI_WIDGET_KEY] === "files") { return true; } if (schema.items) { - const itemsSchema = retrieveSchema( + const itemsSchema = retrieveSchema( validator, - schema.items as RJSFSchema, + schema.items as S, rootSchema ); return itemsSchema.type === "string" && itemsSchema.format === "data-url"; diff --git a/packages/utils/src/schema/isMultiSelect.ts b/packages/utils/src/schema/isMultiSelect.ts index fa30e2d5c2..b55d124e48 100644 --- a/packages/utils/src/schema/isMultiSelect.ts +++ b/packages/utils/src/schema/isMultiSelect.ts @@ -1,4 +1,4 @@ -import { RJSFSchema, ValidatorType } from "../types"; +import { RJSFSchema, StrictRJSFSchema, ValidatorType } from "../types"; import isSelect from "./isSelect"; @@ -9,11 +9,10 @@ import isSelect from "./isSelect"; * @param [rootSchema] - The root schema, used to primarily to look up `$ref`s * @returns - True if schema contains a multi-select, otherwise false */ -export default function isMultiSelect( - validator: ValidatorType, - schema: RJSFSchema, - rootSchema?: RJSFSchema -) { +export default function isMultiSelect< + T = any, + S extends StrictRJSFSchema = RJSFSchema +>(validator: ValidatorType, schema: S, rootSchema?: S) { if ( !schema.uniqueItems || !schema.items || @@ -21,5 +20,5 @@ export default function isMultiSelect( ) { return false; } - return isSelect(validator, schema.items as RJSFSchema, rootSchema); + return isSelect(validator, schema.items as S, rootSchema); } diff --git a/packages/utils/src/schema/isSelect.ts b/packages/utils/src/schema/isSelect.ts index 0236850287..3c3fb60a66 100644 --- a/packages/utils/src/schema/isSelect.ts +++ b/packages/utils/src/schema/isSelect.ts @@ -1,5 +1,5 @@ import isConstant from "../isConstant"; -import { RJSFSchema, ValidatorType } from "../types"; +import { RJSFSchema, StrictRJSFSchema, ValidatorType } from "../types"; import retrieveSchema from "./retrieveSchema"; /** Checks to see if the `schema` combination represents a select @@ -9,12 +9,16 @@ import retrieveSchema from "./retrieveSchema"; * @param [rootSchema] - The root schema, used to primarily to look up `$ref`s * @returns - True if schema contains a select, otherwise false */ -export default function isSelect( - validator: ValidatorType, - theSchema: RJSFSchema, - rootSchema: RJSFSchema = {} -) { - const schema = retrieveSchema(validator, theSchema, rootSchema, undefined); +export default function isSelect< + T = any, + S extends StrictRJSFSchema = RJSFSchema +>(validator: ValidatorType, theSchema: S, rootSchema: S = {} as S) { + const schema = retrieveSchema( + validator, + theSchema, + rootSchema, + undefined + ); const altSchemas = schema.oneOf || schema.anyOf; if (Array.isArray(schema.enum)) { return true; diff --git a/packages/utils/src/schema/mergeValidationData.ts b/packages/utils/src/schema/mergeValidationData.ts index e98b9eeade..188f161d4e 100644 --- a/packages/utils/src/schema/mergeValidationData.ts +++ b/packages/utils/src/schema/mergeValidationData.ts @@ -1,7 +1,13 @@ import isEmpty from "lodash/isEmpty"; import mergeObjects from "../mergeObjects"; -import { ErrorSchema, ValidationData, ValidatorType } from "../types"; +import { + ErrorSchema, + RJSFSchema, + StrictRJSFSchema, + ValidationData, + ValidatorType, +} from "../types"; /** Merges the errors in `additionalErrorSchema` into the existing `validationData` by combining the hierarchies in the * two `ErrorSchema`s and then appending the error list from the `additionalErrorSchema` obtained by calling @@ -13,8 +19,11 @@ import { ErrorSchema, ValidationData, ValidatorType } from "../types"; * @param [additionalErrorSchema] - The additional set of errors in an `ErrorSchema` * @returns - The `validationData` with the additional errors from `additionalErrorSchema` merged into it, if provided. */ -export default function mergeValidationData( - validator: ValidatorType, +export default function mergeValidationData< + T = any, + S extends StrictRJSFSchema = RJSFSchema +>( + validator: ValidatorType, validationData: ValidationData, additionalErrorSchema?: ErrorSchema ): ValidationData { diff --git a/packages/utils/src/schema/retrieveSchema.ts b/packages/utils/src/schema/retrieveSchema.ts index 66c79992d3..d120a5ca11 100644 --- a/packages/utils/src/schema/retrieveSchema.ts +++ b/packages/utils/src/schema/retrieveSchema.ts @@ -18,7 +18,7 @@ import mergeSchemas from "../mergeSchemas"; import { GenericObjectType, RJSFSchema, - RJSFSchemaDefinition, + StrictRJSFSchema, ValidatorType, } from "../types"; import getMatchingOption from "./getMatchingOption"; @@ -26,18 +26,16 @@ import getMatchingOption from "./getMatchingOption"; /** Resolves a conditional block (if/else/then) by removing the condition and merging the appropriate conditional branch * with the rest of the schema * - * @param validator - An implementation of the `ValidatorType` interface that is used to detect valid schema conditions + * @param validator - An implementation of the `ValidatorType` interface that is used to detect valid schema conditions * @param schema - The schema for which resolving a condition is desired * @param rootSchema - The root schema that will be forwarded to all the APIs * @param formData - The current formData to assist retrieving a schema * @returns - A schema with the appropriate condition resolved */ -export function resolveCondition( - validator: ValidatorType, - schema: RJSFSchema, - rootSchema: RJSFSchema, - formData: T -) { +export function resolveCondition< + T = any, + S extends StrictRJSFSchema = RJSFSchema +>(validator: ValidatorType, schema: S, rootSchema: S, formData: T) { const { if: expression, then, @@ -46,7 +44,7 @@ export function resolveCondition( } = schema; const conditionalSchema = validator.isValid( - expression as RJSFSchema, + expression as S, formData, rootSchema ) @@ -54,19 +52,19 @@ export function resolveCondition( : otherwise; if (conditionalSchema && typeof conditionalSchema !== "boolean") { - return retrieveSchema( + return retrieveSchema( validator, mergeSchemas( resolvedSchemaLessConditional, retrieveSchema(validator, conditionalSchema, rootSchema, formData) - ), + ) as S, rootSchema, formData ); } - return retrieveSchema( + return retrieveSchema( validator, - resolvedSchemaLessConditional, + resolvedSchemaLessConditional as S, rootSchema, formData ); @@ -75,37 +73,42 @@ export function resolveCondition( /** Resolves references and dependencies within a schema and its 'allOf' children. * Called internally by retrieveSchema. * - * @param validator - An implementation of the `ValidatorType` interface that will be forwarded to all the APIs + * @param validator - An implementation of the `ValidatorType` interface that will be forwarded to all the APIs * @param schema - The schema for which resolving a schema is desired * @param [rootSchema={}] - The root schema that will be forwarded to all the APIs * @param [formData] - The current formData, if any, to assist retrieving a schema * @returns - The schema having its references and dependencies resolved */ -export function resolveSchema( - validator: ValidatorType, - schema: RJSFSchema, - rootSchema: RJSFSchema = {}, +export function resolveSchema( + validator: ValidatorType, + schema: S, + rootSchema: S = {} as S, formData?: T -): RJSFSchema { +): S { if (REF_KEY in schema) { - return resolveReference(validator, schema, rootSchema, formData); + return resolveReference(validator, schema, rootSchema, formData); } if (DEPENDENCIES_KEY in schema) { - const resolvedSchema = resolveDependencies( + const resolvedSchema = resolveDependencies( validator, schema, rootSchema, formData ); - return retrieveSchema(validator, resolvedSchema, rootSchema, formData); + return retrieveSchema( + validator, + resolvedSchema, + rootSchema, + formData + ); } if (ALL_OF_KEY in schema) { return { ...schema, allOf: schema.allOf!.map((allOfSubschema) => - retrieveSchema( + retrieveSchema( validator, - allOfSubschema as RJSFSchema, + allOfSubschema as S, rootSchema, formData ) @@ -118,24 +121,22 @@ export function resolveSchema( /** Resolves references within a schema and its 'allOf' children. * - * @param validator - An implementation of the `ValidatorType` interface that will be forwarded to all the APIs + * @param validator - An implementation of the `ValidatorType` interface that will be forwarded to all the APIs * @param schema - The schema for which resolving a reference is desired * @param rootSchema - The root schema that will be forwarded to all the APIs * @param [formData] - The current formData, if any, to assist retrieving a schema * @returns - The schema having its references resolved */ -export function resolveReference( - validator: ValidatorType, - schema: RJSFSchema, - rootSchema: RJSFSchema, - formData?: T -): RJSFSchema { +export function resolveReference< + T = any, + S extends StrictRJSFSchema = RJSFSchema +>(validator: ValidatorType, schema: S, rootSchema: S, formData?: T): S { // Retrieve the referenced schema definition. - const $refSchema = findSchemaDefinition(schema.$ref, rootSchema); + const $refSchema = findSchemaDefinition(schema.$ref, rootSchema); // Drop the $ref property of the source schema. const { $ref, ...localSchema } = schema; // Update referenced schema definition with local schema properties. - return retrieveSchema( + return retrieveSchema( validator, { ...$refSchema, ...localSchema }, rootSchema, @@ -145,18 +146,21 @@ export function resolveReference( /** Creates new 'properties' items for each key in the `formData` * - * @param validator - An implementation of the `ValidatorType` interface that will be used when necessary + * @param validator - An implementation of the `ValidatorType` interface that will be used when necessary * @param theSchema - The schema for which the existing additional properties is desired * @param [rootSchema] - The root schema, used to primarily to look up `$ref`s * @param validator * @param [aFormData] - The current formData, if any, to assist retrieving a schema * @returns - The updated schema with additional properties stubbed */ -export function stubExistingAdditionalProperties( - validator: ValidatorType, - theSchema: RJSFSchema, - rootSchema?: RJSFSchema, +export function stubExistingAdditionalProperties< + T = any, + S extends StrictRJSFSchema = RJSFSchema +>( + validator: ValidatorType, + theSchema: S, + rootSchema?: S, aFormData?: T -): RJSFSchema { +): S { // Clone the schema so we don't ruin the consumer's original const schema = { ...theSchema, @@ -172,12 +176,12 @@ export function stubExistingAdditionalProperties( return; } - let additionalProperties: RJSFSchema = {}; + let additionalProperties: S["additionalProperties"] = {}; if (typeof schema.additionalProperties !== "boolean") { if (REF_KEY in schema.additionalProperties!) { - additionalProperties = retrieveSchema( + additionalProperties = retrieveSchema( validator, - { $ref: get(schema.additionalProperties, [REF_KEY]) }, + { $ref: get(schema.additionalProperties, [REF_KEY]) } as S, rootSchema, formData as T ); @@ -203,22 +207,25 @@ export function stubExistingAdditionalProperties( * resolved and merged into the `schema` given a `validator`, `rootSchema` and `rawFormData` that is used to do the * potentially recursive resolution. * - * @param validator - An implementation of the `ValidatorType` interface that will be forwarded to all the APIs + * @param validator - An implementation of the `ValidatorType` interface that will be forwarded to all the APIs * @param schema - The schema for which retrieving a schema is desired * @param [rootSchema={}] - The root schema that will be forwarded to all the APIs * @param [rawFormData] - The current formData, if any, to assist retrieving a schema * @returns - The schema having its conditions, additional properties, references and dependencies resolved */ -export default function retrieveSchema( - validator: ValidatorType, - schema: RJSFSchema, - rootSchema: RJSFSchema = {}, +export default function retrieveSchema< + T = any, + S extends StrictRJSFSchema = RJSFSchema +>( + validator: ValidatorType, + schema: S, + rootSchema: S = {} as S, rawFormData?: T -): RJSFSchema { +): S { if (!isObject(schema)) { - return {}; + return {} as S; } - let resolvedSchema = resolveSchema( + let resolvedSchema = resolveSchema( validator, schema, rootSchema, @@ -226,7 +233,12 @@ export default function retrieveSchema( ); if ("if" in schema) { - return resolveCondition(validator, schema, rootSchema, rawFormData as T); + return resolveCondition( + validator, + schema, + rootSchema, + rawFormData as T + ); } const formData: GenericObjectType = rawFormData || {}; @@ -237,10 +249,10 @@ export default function retrieveSchema( Object.entries(resolvedSchema.properties).forEach((entries) => { const propName = entries[0]; - const propSchema = entries[1] as RJSFSchema; + const propSchema = entries[1] as S; const rawPropData = formData[propName]; const propData = isObject(rawPropData) ? rawPropData : {}; - const resolvedPropSchema = retrieveSchema( + const resolvedPropSchema = retrieveSchema( validator, propSchema, rootSchema, @@ -263,18 +275,18 @@ export default function retrieveSchema( resolvedSchema = mergeAllOf({ ...resolvedSchema, allOf: resolvedSchema.allOf, - }); + }) as S; } catch (e) { console.warn("could not merge subschemas in allOf:\n" + e); const { allOf, ...resolvedSchemaWithoutAllOf } = resolvedSchema; - return resolvedSchemaWithoutAllOf; + return resolvedSchemaWithoutAllOf as S; } } const hasAdditionalProperties = ADDITIONAL_PROPERTIES_KEY in resolvedSchema && resolvedSchema.additionalProperties !== false; if (hasAdditionalProperties) { - return stubExistingAdditionalProperties( + return stubExistingAdditionalProperties( validator, resolvedSchema, rootSchema, @@ -286,41 +298,39 @@ export default function retrieveSchema( /** Resolves dependencies within a schema and its 'allOf' children. * - * @param validator - An implementation of the `ValidatorType` interface that will be forwarded to all the APIs + * @param validator - An implementation of the `ValidatorType` interface that will be forwarded to all the APIs * @param schema - The schema for which resolving a dependency is desired * @param rootSchema - The root schema that will be forwarded to all the APIs * @param [formData] - The current formData, if any, to assist retrieving a schema * @returns - The schema with its dependencies resolved */ -export function resolveDependencies( - validator: ValidatorType, - schema: RJSFSchema, - rootSchema: RJSFSchema, - formData?: T -): RJSFSchema { +export function resolveDependencies< + T = any, + S extends StrictRJSFSchema = RJSFSchema +>(validator: ValidatorType, schema: S, rootSchema: S, formData?: T): S { // Drop the dependencies from the source schema. const { dependencies, ...remainingSchema } = schema; - let resolvedSchema = remainingSchema; + let resolvedSchema: S = remainingSchema as S; if (Array.isArray(resolvedSchema.oneOf)) { resolvedSchema = resolvedSchema.oneOf[ - getMatchingOption( + getMatchingOption( validator, formData, - resolvedSchema.oneOf as RJSFSchema[], + resolvedSchema.oneOf as S[], rootSchema ) - ] as RJSFSchema; + ] as S; } else if (Array.isArray(resolvedSchema.anyOf)) { resolvedSchema = resolvedSchema.anyOf[ - getMatchingOption( + getMatchingOption( validator, formData, - resolvedSchema.anyOf as RJSFSchema[], + resolvedSchema.anyOf as S[], rootSchema ) - ] as RJSFSchema; + ] as S; } - return processDependencies( + return processDependencies( validator, dependencies, resolvedSchema, @@ -331,20 +341,23 @@ export function resolveDependencies( /** Processes all the `dependencies` recursively into the `resolvedSchema` as needed * - * @param validator - An implementation of the `ValidatorType` interface that will be forwarded to all the APIs + * @param validator - An implementation of the `ValidatorType` interface that will be forwarded to all the APIs * @param dependencies - The set of dependencies that needs to be processed * @param resolvedSchema - The schema for which processing dependencies is desired * @param rootSchema - The root schema that will be forwarded to all the APIs * @param [formData] - The current formData, if any, to assist retrieving a schema * @returns - The schema with the `dependencies` resolved into it */ -export function processDependencies( - validator: ValidatorType, - dependencies: RJSFSchema["dependencies"], - resolvedSchema: RJSFSchema, - rootSchema: RJSFSchema, +export function processDependencies< + T = any, + S extends StrictRJSFSchema = RJSFSchema +>( + validator: ValidatorType, + dependencies: S["dependencies"], + resolvedSchema: S, + rootSchema: S, formData?: T -): RJSFSchema { +): S { let schema = resolvedSchema; // Process dependencies updating the local schema properties as appropriate. for (const dependencyKey in dependencies) { @@ -363,16 +376,16 @@ export function processDependencies( if (Array.isArray(dependencyValue)) { schema = withDependentProperties(schema, dependencyValue); } else if (isObject(dependencyValue)) { - schema = withDependentSchema( + schema = withDependentSchema( validator, schema, rootSchema, dependencyKey, - dependencyValue as RJSFSchema, + dependencyValue as S, formData ); } - return processDependencies( + return processDependencies( validator, remainingDependencies, schema, @@ -389,10 +402,9 @@ export function processDependencies( * @param [additionallyRequired] - An optional array of additionally required names * @returns - The schema with the additional required values merged in */ -export function withDependentProperties( - schema: RJSFSchema, - additionallyRequired?: string[] -) { +export function withDependentProperties< + S extends StrictRJSFSchema = RJSFSchema +>(schema: S, additionallyRequired?: string[]) { if (!additionallyRequired) { return schema; } @@ -404,7 +416,7 @@ export function withDependentProperties( /** Merges a dependent schema into the `schema` dealing with oneOfs and references * - * @param validator - An implementation of the `ValidatorType` interface that will be forwarded to all the APIs + * @param validator - An implementation of the `ValidatorType` interface that will be forwarded to all the APIs * @param schema - The schema for which resolving a dependent schema is desired * @param rootSchema - The root schema that will be forwarded to all the APIs * @param dependencyKey - The key name of the dependency @@ -412,21 +424,24 @@ export function withDependentProperties( * @param formData- The current formData to assist retrieving a schema * @returns - The schema with the dependent schema resolved into it */ -export function withDependentSchema( - validator: ValidatorType, - schema: RJSFSchema, - rootSchema: RJSFSchema, +export function withDependentSchema< + T = any, + S extends StrictRJSFSchema = RJSFSchema +>( + validator: ValidatorType, + schema: S, + rootSchema: S, dependencyKey: string, - dependencyValue: RJSFSchema, + dependencyValue: S, formData?: T ) { - const { oneOf, ...dependentSchema } = retrieveSchema( + const { oneOf, ...dependentSchema } = retrieveSchema( validator, dependencyValue, rootSchema, formData ); - schema = mergeSchemas(schema, dependentSchema); + schema = mergeSchemas(schema, dependentSchema) as S; // Since it does not contain oneOf, we return the original schema. if (oneOf === undefined) { return schema; @@ -436,14 +451,14 @@ export function withDependentSchema( if (typeof subschema === "boolean" || !(REF_KEY in subschema)) { return subschema; } - return resolveReference( + return resolveReference( validator, - subschema as RJSFSchema, + subschema as S, rootSchema, formData ); }); - return withExactlyOneSubschema( + return withExactlyOneSubschema( validator, schema, rootSchema, @@ -455,47 +470,50 @@ export function withDependentSchema( /** Returns a `schema` with the best choice from the `oneOf` options merged into it * - * @param validator - An implementation of the `ValidatorType` interface that will be used to validate oneOf options + * @param validator - An implementation of the `ValidatorType` interface that will be used to validate oneOf options * @param schema - The schema for which resolving a oneOf subschema is desired * @param rootSchema - The root schema that will be forwarded to all the APIs * @param dependencyKey - The key name of the oneOf dependency * @param oneOf - The list of schemas representing the oneOf options * @param [formData] - The current formData to assist retrieving a schema - * @returns The schema with best choice of oneOf schemas merged into + * @returns The schema with the best choice of oneOf schemas merged into */ -export function withExactlyOneSubschema( - validator: ValidatorType, - schema: RJSFSchema, - rootSchema: RJSFSchema, +export function withExactlyOneSubschema< + T = any, + S extends StrictRJSFSchema = RJSFSchema +>( + validator: ValidatorType, + schema: S, + rootSchema: S, dependencyKey: string, - oneOf: RJSFSchemaDefinition[], + oneOf: S["oneOf"], formData?: T -) { - const validSubschemas = oneOf.filter((subschema) => { - if (typeof subschema === "boolean" || !subschema.properties) { +): S { + const validSubschemas = oneOf!.filter((subschema) => { + if (typeof subschema === "boolean" || !subschema || !subschema.properties) { return false; } const { [dependencyKey]: conditionPropertySchema } = subschema.properties; if (conditionPropertySchema) { - const conditionSchema: RJSFSchema = { + const conditionSchema: S = { type: "object", properties: { [dependencyKey]: conditionPropertySchema, }, - }; + } as S; const { errors } = validator.validateFormData(formData, conditionSchema); return errors.length === 0; } return false; }); - if (validSubschemas.length !== 1) { + if (validSubschemas!.length !== 1) { console.warn( "ignoring oneOf in dependencies because there isn't exactly one subschema that is valid" ); return schema; } - const subschema: RJSFSchema = validSubschemas[0] as RJSFSchema; + const subschema: S = validSubschemas[0] as S; const [dependentSubschema] = splitKeyElementFromObject( dependencyKey, subschema.properties as GenericObjectType @@ -503,6 +521,6 @@ export function withExactlyOneSubschema( const dependentSchema = { ...subschema, properties: dependentSubschema }; return mergeSchemas( schema, - retrieveSchema(validator, dependentSchema, rootSchema, formData) - ); + retrieveSchema(validator, dependentSchema, rootSchema, formData) + ) as S; } diff --git a/packages/utils/src/schema/toIdSchema.ts b/packages/utils/src/schema/toIdSchema.ts index ddcf889647..13e9e7c9fb 100644 --- a/packages/utils/src/schema/toIdSchema.ts +++ b/packages/utils/src/schema/toIdSchema.ts @@ -9,7 +9,12 @@ import { REF_KEY, } from "../constants"; import isObject from "../isObject"; -import { IdSchema, RJSFSchema, ValidatorType } from "../types"; +import { + IdSchema, + RJSFSchema, + StrictRJSFSchema, + ValidatorType, +} from "../types"; import retrieveSchema from "./retrieveSchema"; /** Generates an `IdSchema` object for the `schema`, recursively @@ -23,17 +28,25 @@ import retrieveSchema from "./retrieveSchema"; * @param [idSeparator='_'] - The separator to use for the path segments in the id * @returns - The `IdSchema` object for the `schema` */ -export default function toIdSchema( - validator: ValidatorType, - schema: RJSFSchema, +export default function toIdSchema< + T = any, + S extends StrictRJSFSchema = RJSFSchema +>( + validator: ValidatorType, + schema: S, id?: string | null, - rootSchema?: RJSFSchema, + rootSchema?: S, formData?: T, idPrefix = "root", idSeparator = "_" ): IdSchema { if (REF_KEY in schema || DEPENDENCIES_KEY in schema || ALL_OF_KEY in schema) { - const _schema = retrieveSchema(validator, schema, rootSchema, formData); + const _schema = retrieveSchema( + validator, + schema, + rootSchema, + formData + ); return toIdSchema( validator, _schema, @@ -45,9 +58,9 @@ export default function toIdSchema( ); } if (ITEMS_KEY in schema && !get(schema, [ITEMS_KEY, REF_KEY])) { - return toIdSchema( + return toIdSchema( validator, - get(schema, ITEMS_KEY) as RJSFSchema, + get(schema, ITEMS_KEY) as S, id, rootSchema, formData, @@ -61,7 +74,7 @@ export default function toIdSchema( for (const name in schema.properties) { const field = get(schema, [PROPERTIES_KEY, name]); const fieldId = idSchema[ID_KEY] + idSeparator + name; - idSchema[name] = toIdSchema( + idSchema[name] = toIdSchema( validator, isObject(field) ? field : {}, fieldId, diff --git a/packages/utils/src/schema/toPathSchema.ts b/packages/utils/src/schema/toPathSchema.ts index 2d849d3e54..7dc7ea7fbd 100644 --- a/packages/utils/src/schema/toPathSchema.ts +++ b/packages/utils/src/schema/toPathSchema.ts @@ -11,7 +11,12 @@ import { REF_KEY, RJSF_ADDITONAL_PROPERTIES_FLAG, } from "../constants"; -import { PathSchema, RJSFSchema, ValidatorType } from "../types"; +import { + PathSchema, + RJSFSchema, + StrictRJSFSchema, + ValidatorType, +} from "../types"; import retrieveSchema from "./retrieveSchema"; /** Generates an `PathSchema` object for the `schema`, recursively @@ -23,16 +28,24 @@ import retrieveSchema from "./retrieveSchema"; * @param [formData] - The current formData, if any, to assist retrieving a schema * @returns - The `PathSchema` object for the `schema` */ -export default function toPathSchema( - validator: ValidatorType, - schema: RJSFSchema, +export default function toPathSchema< + T = any, + S extends StrictRJSFSchema = RJSFSchema +>( + validator: ValidatorType, + schema: S, name = "", - rootSchema?: RJSFSchema, + rootSchema?: S, formData?: T ): PathSchema { if (REF_KEY in schema || DEPENDENCIES_KEY in schema || ALL_OF_KEY in schema) { - const _schema = retrieveSchema(validator, schema, rootSchema, formData); - return toPathSchema(validator, _schema, name, rootSchema, formData); + const _schema = retrieveSchema( + validator, + schema, + rootSchema, + formData + ); + return toPathSchema(validator, _schema, name, rootSchema, formData); } const pathSchema: PathSchema = { @@ -59,7 +72,7 @@ export default function toPathSchema( } else if (PROPERTIES_KEY in schema) { for (const property in schema.properties) { const field = get(schema, [PROPERTIES_KEY, property]); - pathSchema[property] = toPathSchema( + pathSchema[property] = toPathSchema( validator, field, `${name}.${property}`, diff --git a/packages/utils/src/schemaRequiresTrueValue.ts b/packages/utils/src/schemaRequiresTrueValue.ts index 54d0219470..8cf9af7154 100644 --- a/packages/utils/src/schemaRequiresTrueValue.ts +++ b/packages/utils/src/schemaRequiresTrueValue.ts @@ -1,4 +1,4 @@ -import { RJSFSchema, RJSFSchemaDefinition } from "./types"; +import { RJSFSchema, StrictRJSFSchema } from "./types"; /** Check to see if a `schema` specifies that a value must be true. This happens when: * - `schema.const` is truthy @@ -9,7 +9,9 @@ import { RJSFSchema, RJSFSchemaDefinition } from "./types"; * @param schema - The schema to check * @returns - True if the schema specifies a value that must be true, false otherwise */ -export default function schemaRequiresTrueValue(schema: RJSFSchema): boolean { +export default function schemaRequiresTrueValue< + S extends StrictRJSFSchema = RJSFSchema +>(schema: S): boolean { // Check if const is a truthy value if (schema.const) { return true; @@ -22,18 +24,18 @@ export default function schemaRequiresTrueValue(schema: RJSFSchema): boolean { // If anyOf has a single value, evaluate the subschema if (schema.anyOf && schema.anyOf.length === 1) { - return schemaRequiresTrueValue(schema.anyOf[0] as RJSFSchema); + return schemaRequiresTrueValue(schema.anyOf[0] as S); } // If oneOf has a single value, evaluate the subschema if (schema.oneOf && schema.oneOf.length === 1) { - return schemaRequiresTrueValue(schema.oneOf[0] as RJSFSchema); + return schemaRequiresTrueValue(schema.oneOf[0] as S); } // Evaluate each subschema in allOf, to see if one of them requires a true value if (schema.allOf) { - const schemaSome = (subSchema: RJSFSchemaDefinition) => - schemaRequiresTrueValue(subSchema as RJSFSchema); + const schemaSome = (subSchema: S["additionalProperties"]) => + schemaRequiresTrueValue(subSchema as S); return schema.allOf.some(schemaSome); } diff --git a/packages/utils/src/toConstant.ts b/packages/utils/src/toConstant.ts index d6cb292b92..c9e14a14fc 100644 --- a/packages/utils/src/toConstant.ts +++ b/packages/utils/src/toConstant.ts @@ -1,5 +1,5 @@ import { CONST_KEY, ENUM_KEY } from "./constants"; -import { RJSFSchema } from "./types"; +import { RJSFSchema, StrictRJSFSchema } from "./types"; /** Returns the constant value from the schema when it is either a single value enum or has a const key. Otherwise * throws an error. @@ -8,7 +8,9 @@ import { RJSFSchema } from "./types"; * @returns - The constant value for the schema * @throws - Error when the schema does not have a constant value */ -export default function toConstant(schema: RJSFSchema) { +export default function toConstant( + schema: S +) { if ( ENUM_KEY in schema && Array.isArray(schema.enum) && diff --git a/packages/utils/src/types.ts b/packages/utils/src/types.ts index 16c99893f4..3d63b63394 100644 --- a/packages/utils/src/types.ts +++ b/packages/utils/src/types.ts @@ -1,5 +1,5 @@ import React from "react"; -import { JSONSchema7, JSONSchema7Definition } from "json-schema"; +import { JSONSchema7 } from "json-schema"; /** The representation of any generic object type, usually used as an intersection on other types to make them more * flexible in the properties they support (i.e. anything else) @@ -11,12 +11,11 @@ export type GenericObjectType = { /** Map the JSONSchema7 to our own type so that we can easily bump to JSONSchema8 at some future date and only have to * update this one type. */ -export type RJSFSchema = JSONSchema7; +export type StrictRJSFSchema = JSONSchema7; -/** Map the JSONSchema7Definition to our own type so that we can easily bump to JSONSchema8Definition at some future - * date and only have to update this one type. +/** Allow for more flexible schemas (i.e. draft-2019) than the strict JSONSchema7 */ -export type RJSFSchemaDefinition = JSONSchema7Definition; +export type RJSFSchema = StrictRJSFSchema & GenericObjectType; /** The interface representing a Date object that contains an optional time */ export interface DateObject { @@ -129,7 +128,11 @@ export type FormValidation = FieldValidation & { }; /** The properties that are passed to an `ErrorListTemplate` implementation */ -export type ErrorListProps = { +export type ErrorListProps< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +> = { /** The errorSchema constructed by `Form` */ errorSchema: ErrorSchema; /** An array of the errors */ @@ -137,13 +140,17 @@ export type ErrorListProps = { /** The `formContext` object that was passed to `Form` */ formContext?: F; /** The schema that was passed to `Form` */ - schema: RJSFSchema; + schema: S; /** The uiSchema that was passed to `Form` */ - uiSchema?: UiSchema; + uiSchema?: UiSchema; }; /** The properties that are passed to an `FieldErrorTemplate` implementation */ -export type FieldErrorProps = { +export type FieldErrorProps< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +> = { /** The errorSchema constructed by `Form` */ errorSchema?: ErrorSchema; /** An array of the errors */ @@ -151,119 +158,144 @@ export type FieldErrorProps = { /** The tree of unique ids for every child field */ idSchema: IdSchema; /** The schema that was passed to field */ - schema: RJSFSchema; + schema: S; /** The uiSchema that was passed to field */ - uiSchema?: UiSchema; + uiSchema?: UiSchema; /** The `registry` object */ - registry: Registry; + registry: Registry; }; /** The properties that are passed to an `FieldHelpTemplate` implementation */ -export type FieldHelpProps = { +export type FieldHelpProps< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +> = { /** The help information to be rendered */ help?: string | React.ReactElement; /** The tree of unique ids for every child field */ idSchema: IdSchema; /** The schema that was passed to field */ - schema: RJSFSchema; + schema: S; /** The uiSchema that was passed to field */ - uiSchema?: UiSchema; + uiSchema?: UiSchema; /** Flag indicating whether there are errors associated with this field */ hasErrors?: boolean; /** The `registry` object */ - registry: Registry; + registry: Registry; }; /** The set of `Fields` stored in the `Registry` */ -export type RegistryFieldsType = { +export type RegistryFieldsType< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +> = { /** A `Field` indexed by `name` */ - [name: string]: Field; + [name: string]: Field; }; /** The set of `Widgets` stored in the `Registry` */ -export type RegistryWidgetsType = { +export type RegistryWidgetsType< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +> = { /** A `Widget` indexed by `name` */ - [name: string]: Widget; + [name: string]: Widget; }; /** The set of RJSF templates that can be overridden by themes or users */ -export interface TemplatesType { +export interface TemplatesType< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +> { /** The template to use while rendering normal or fixed array fields */ - ArrayFieldTemplate: React.ComponentType>; + ArrayFieldTemplate: React.ComponentType>; /** The template to use while rendering the description for an array field */ ArrayFieldDescriptionTemplate: React.ComponentType< - ArrayFieldDescriptionProps + ArrayFieldDescriptionProps >; /** The template to use while rendering an item in an array field */ - ArrayFieldItemTemplate: React.ComponentType>; + ArrayFieldItemTemplate: React.ComponentType< + ArrayFieldTemplateItemType + >; /** The template to use while rendering the title for an array field */ - ArrayFieldTitleTemplate: React.ComponentType>; + ArrayFieldTitleTemplate: React.ComponentType>; /** The template to use while rendering the standard html input */ - BaseInputTemplate: React.ComponentType>; + BaseInputTemplate: React.ComponentType>; /** The template to use for rendering the description of a field */ - DescriptionFieldTemplate: React.ComponentType>; + DescriptionFieldTemplate: React.ComponentType>; /** The template to use while rendering the errors for the whole form */ - ErrorListTemplate: React.ComponentType>; + ErrorListTemplate: React.ComponentType>; /** The template to use while rendering the errors for a single field */ - FieldErrorTemplate: React.ComponentType>; + FieldErrorTemplate: React.ComponentType>; /** The template to use while rendering the errors for a single field */ - FieldHelpTemplate: React.ComponentType>; + FieldHelpTemplate: React.ComponentType>; /** The template to use while rendering a field */ - FieldTemplate: React.ComponentType>; + FieldTemplate: React.ComponentType>; /** The template to use while rendering an object */ - ObjectFieldTemplate: React.ComponentType>; + ObjectFieldTemplate: React.ComponentType>; /** The template to use for rendering the title of a field */ - TitleFieldTemplate: React.ComponentType>; + TitleFieldTemplate: React.ComponentType>; /** The template to use for rendering information about an unsupported field type in the schema */ - UnsupportedFieldTemplate: React.ComponentType>; + UnsupportedFieldTemplate: React.ComponentType>; /** The template to use for rendering a field that allows a user to add additional properties */ WrapIfAdditionalTemplate: React.ComponentType< - WrapIfAdditionalTemplateProps + WrapIfAdditionalTemplateProps >; /** The set of templates associated with buttons in the form */ ButtonTemplates: { /** The template to use for the main `Submit` button */ - SubmitButton: React.ComponentType>; + SubmitButton: React.ComponentType>; /** The template to use for the Add button used for AdditionalProperties and Array items */ - AddButton: React.ComponentType>; + AddButton: React.ComponentType>; /** The template to use for the Move Down button used for Array items */ - MoveDownButton: React.ComponentType>; + MoveDownButton: React.ComponentType>; /** The template to use for the Move Up button used for Array items */ - MoveUpButton: React.ComponentType>; + MoveUpButton: React.ComponentType>; /** The template to use for the Remove button used for AdditionalProperties and Array items */ - RemoveButton: React.ComponentType>; + RemoveButton: React.ComponentType>; }; } /** The object containing the registered core, theme and custom fields and widgets as well as the root schema, form * context, schema utils and templates. */ -export interface Registry { +export interface Registry< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +> { /** The set of all fields used by the `Form`. Includes fields from `core`, theme-specific fields and any custom * registered fields */ - fields: RegistryFieldsType; + fields: RegistryFieldsType; /** The set of templates used by the `Form`. Includes templates from `core`, theme-specific fields and any custom * registered templates */ - templates: TemplatesType; + templates: TemplatesType; /** The set of all widgets used by the `Form`. Includes widgets from `core`, theme-specific widgets and any custom * registered widgets */ - widgets: RegistryWidgetsType; + widgets: RegistryWidgetsType; /** The `formContext` object that was passed to `Form` */ formContext: F; /** The root schema, as passed to the `Form`, which can contain referenced definitions */ - rootSchema: RJSFSchema; + rootSchema: S; /** The current implementation of the `SchemaUtilsType` (from `@rjsf/utils`) in use by the `Form`. Used to call any * of the validation-schema-based utility functions */ - schemaUtils: SchemaUtilsType; + schemaUtils: SchemaUtilsType; } /** The properties that are passed to a Field implementation */ -export interface FieldProps - extends GenericObjectType, +export interface FieldProps< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +> extends GenericObjectType, Pick< React.HTMLAttributes, Exclude< @@ -272,9 +304,9 @@ export interface FieldProps > > { /** The JSON subschema object for this field */ - schema: RJSFSchema; + schema: S; /** The uiSchema for this field */ - uiSchema?: UiSchema; + uiSchema?: UiSchema; /** The tree of unique ids for every child field */ idSchema: IdSchema; /** The data for this field */ @@ -302,14 +334,22 @@ export interface FieldProps /** The unique name of the field, usually derived from the name of the property in the JSONSchema */ name: string; /** The `registry` object */ - registry: Registry; + registry: Registry; } /** The definition of a React-based Field component */ -export type Field = React.ComponentType>; +export type Field< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +> = React.ComponentType>; /** The properties that are passed to a FieldTemplate implementation */ -export type FieldTemplateProps = { +export type FieldTemplateProps< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +> = { /** The id of the field in the hierarchy. You can use it to render a label targeting the wrapped widget */ id: string; /** A string containing the base CSS classes, merged with any custom ones defined in your uiSchema */ @@ -349,9 +389,9 @@ export type FieldTemplateProps = { */ displayLabel?: boolean; /** The schema object for this field */ - schema: RJSFSchema; + schema: S; /** The uiSchema object for this field */ - uiSchema?: UiSchema; + uiSchema?: UiSchema; /** The `formContext` object that was passed to `Form` */ formContext?: F; /** The formData for this field */ @@ -363,56 +403,69 @@ export type FieldTemplateProps = { /** The property drop/removal event handler; Called when a field is removed in an additionalProperty context */ onDropPropertyClick: (value: string) => () => void; /** The `registry` object */ - registry: Registry; + registry: Registry; }; /** The properties that are passed to the `UnsupportedFieldTemplate` implementation */ -export type UnsupportedFieldProps = { +export type UnsupportedFieldProps< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +> = { /** The schema object for this field */ - schema: RJSFSchema; + schema: S; /** The tree of unique ids for every child field */ idSchema?: IdSchema; /** The reason why the schema field has an unsupported type */ reason: string; /** The `registry` object */ - registry: Registry; + registry: Registry; }; /** The properties that are passed to a `TitleFieldTemplate` implementation */ -export type TitleFieldProps = { +export type TitleFieldProps< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +> = { /** The id of the field title in the hierarchy */ id: string; /** The title for the field being rendered */ title: string; /** The schema object for the field being titled */ - schema: RJSFSchema; + schema: S; /** The uiSchema object for this title field */ - uiSchema?: UiSchema; + uiSchema?: UiSchema; /** A boolean value stating if the field is required */ required?: boolean; /** The `registry` object */ - registry: Registry; + registry: Registry; }; /** The properties that are passed to a `DescriptionFieldTemplate` implementation */ -export type DescriptionFieldProps = { +export type DescriptionFieldProps< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +> = { /** The id of the field description in the hierarchy */ id: string; /** The schema object for the field being described */ - schema: RJSFSchema; + schema: S; /** The uiSchema object for this description field */ - uiSchema?: UiSchema; + uiSchema?: UiSchema; /** The description of the field being rendered */ description: string | React.ReactElement; /** The `registry` object */ - registry: Registry; + registry: Registry; }; /** The properties that are passed to a `ArrayFieldTitleTemplate` implementation */ -export type ArrayFieldTitleProps = Omit< - TitleFieldProps, - "id" | "title" -> & { +export type ArrayFieldTitleProps< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +> = Omit, "id" | "title"> & { /** The title for the field being rendered */ title?: string; /** The idSchema of the field in the hierarchy */ @@ -420,10 +473,11 @@ export type ArrayFieldTitleProps = Omit< }; /** The properties that are passed to a `ArrayFieldDescriptionTemplate` implementation */ -export type ArrayFieldDescriptionProps = Omit< - DescriptionFieldProps, - "id" | "description" -> & { +export type ArrayFieldDescriptionProps< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +> = Omit, "id" | "description"> & { /** The description of the field being rendered */ description?: string | React.ReactElement; /** The idSchema of the field in the hierarchy */ @@ -431,7 +485,11 @@ export type ArrayFieldDescriptionProps = Omit< }; /** The properties of each element in the ArrayFieldTemplateProps.items array */ -export type ArrayFieldTemplateItemType = { +export type ArrayFieldTemplateItemType< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +> = { /** The html for the item's content */ children: React.ReactElement; /** The className string */ @@ -459,13 +517,17 @@ export type ArrayFieldTemplateItemType = { /** A stable, unique key for the array item */ key: string; /** The uiSchema object for this field */ - uiSchema?: UiSchema; + uiSchema?: UiSchema; /** The `registry` object */ - registry: Registry; + registry: Registry; }; /** The properties that are passed to an ArrayFieldTemplate implementation */ -export type ArrayFieldTemplateProps = { +export type ArrayFieldTemplateProps< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +> = { /** A boolean value stating whether new elements can be added to the array */ canAdd?: boolean; /** The className string */ @@ -475,7 +537,7 @@ export type ArrayFieldTemplateProps = { /** An object containing the id for this object & ids for its properties */ idSchema: IdSchema; /** An array of objects representing the items in the array */ - items: ArrayFieldTemplateItemType[]; + items: ArrayFieldTemplateItemType[]; /** A function that adds a new item to the array */ onAddClick: (event?: any) => void; /** A boolean value stating if the array is read-only */ @@ -485,9 +547,9 @@ export type ArrayFieldTemplateProps = { /** A boolean value stating if the field is hiding its errors */ hideError?: boolean; /** The schema object for this array */ - schema: RJSFSchema; + schema: S; /** The uiSchema object for this array field */ - uiSchema?: UiSchema; + uiSchema?: UiSchema; /** A string value containing the title for the array */ title: string; /** The `formContext` object that was passed to Form */ @@ -497,7 +559,7 @@ export type ArrayFieldTemplateProps = { /** An array of strings listing all generated error messages from encountered errors for this widget */ rawErrors?: string[]; /** The `registry` object */ - registry: Registry; + registry: Registry; }; /** The properties of each element in the ObjectFieldTemplateProps.properties array */ @@ -515,7 +577,11 @@ export type ObjectFieldTemplatePropertyType = { }; /** The properties that are passed to an ObjectFieldTemplate implementation */ -export type ObjectFieldTemplateProps = { +export type ObjectFieldTemplateProps< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +> = { /** A string value containing the title for the object */ title: string; /** A string value containing the description for the object */ @@ -525,7 +591,7 @@ export type ObjectFieldTemplateProps = { /** An array of objects representing the properties in the object */ properties: ObjectFieldTemplatePropertyType[]; /** Returns a function that adds a new property to the object (to be used with additionalProperties) */ - onAddClick: (schema: RJSFSchema) => () => void; + onAddClick: (schema: S) => () => void; /** A boolean value stating if the object is read-only */ readonly?: boolean; /** A boolean value stating if the object is required */ @@ -533,9 +599,9 @@ export type ObjectFieldTemplateProps = { /** A boolean value stating if the field is hiding its errors */ hideError?: boolean; /** The schema object for this object */ - schema: RJSFSchema; + schema: S; /** The uiSchema object for this object field */ - uiSchema?: UiSchema; + uiSchema?: UiSchema; /** An object containing the id for this object & ids for its properties */ idSchema: IdSchema; /** The form data for the object */ @@ -543,15 +609,19 @@ export type ObjectFieldTemplateProps = { /** The `formContext` object that was passed to Form */ formContext?: F; /** The `registry` object */ - registry: Registry; + registry: Registry; }; /** The properties that are passed to a WrapIfAdditionalTemplate implementation */ -export type WrapIfAdditionalTemplateProps = { +export type WrapIfAdditionalTemplateProps< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +> = { /** The field or widget component instance for this field row */ children: React.ReactNode; } & Pick< - FieldTemplateProps, + FieldTemplateProps, | "id" | "classNames" | "label" @@ -566,8 +636,11 @@ export type WrapIfAdditionalTemplateProps = { >; /** The properties that are passed to a Widget implementation */ -export interface WidgetProps - extends GenericObjectType, +export interface WidgetProps< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +> extends GenericObjectType, Pick< React.HTMLAttributes, Exclude, "onBlur" | "onFocus"> @@ -575,9 +648,9 @@ export interface WidgetProps /** The generated id for this widget */ id: string; /** The JSONSchema subschema object for this widget */ - schema: RJSFSchema; + schema: S; /** The uiSchema for this widget */ - uiSchema?: UiSchema; + uiSchema?: UiSchema; /** The current value for this widget */ value: any; /** The required status of this widget */ @@ -595,7 +668,7 @@ export interface WidgetProps /** A map of UI Options passed as a prop to the component, including the optional `enumOptions` * which is a special case on top of `UIOptionsType` needed only by widgets */ - options: NonNullable> & { + options: NonNullable> & { /** The enum options list for a type that supports them */ enumOptions?: EnumOptionsType[]; }; @@ -614,21 +687,30 @@ export interface WidgetProps /** An array of strings listing all generated error messages from encountered errors for this widget */ rawErrors?: string[]; /** The `registry` object */ - registry: Registry; + registry: Registry; } /** The definition of a React-based Widget component */ -export type Widget = React.ComponentType>; +export type Widget< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +> = React.ComponentType>; /** The type that defines the props used by the Submit button */ -export type SubmitButtonProps = { +export type SubmitButtonProps< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +> = { /** The uiSchema for this widget */ - uiSchema?: UiSchema; + uiSchema?: UiSchema; }; /** The type that defines the props for an Icon button, extending from a basic HTML button attributes */ export type IconButtonProps< T = any, + S extends StrictRJSFSchema = RJSFSchema, F = any > = React.ButtonHTMLAttributes & { /** An alternative specification for the type of the icon button */ @@ -636,7 +718,7 @@ export type IconButtonProps< /** The name representation or actual react element implementation for the icon */ icon?: string | React.ReactElement; /** The uiSchema for this widget */ - uiSchema?: UiSchema; + uiSchema?: UiSchema; }; /** The type that defines how to change the behavior of the submit button for the form */ @@ -655,13 +737,13 @@ export type UISchemaSubmitButtonOptions = { }; /** This type represents an element used to render an enum option */ -export type EnumOptionsType = { +export type EnumOptionsType = { /** The value for the enum option */ value: any; /** The label for the enum options */ label: string; /** The schema associated with the enum option when the option represents a `oneOf` or `anyOf` choice */ - schema?: RJSFSchema; + schema?: S; }; /** This type remaps the keys of `Type` to prepend `ui:` onto them. As a result it does not need to be exported */ @@ -672,9 +754,11 @@ type MakeUIType = { /** This type represents all the known supported options in the `ui:options` property, kept separate in order to * remap the keys. It also contains all the properties, optionally, of `TemplatesType` except "ButtonTemplates" */ -type UIOptionsBaseType = Partial< - Omit, "ButtonTemplates"> -> & { +type UIOptionsBaseType< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +> = Partial, "ButtonTemplates">> & { /** Any classnames that the user wants to be applied to a field in the ui */ classNames?: string; /** We know that for title, it will be a string, if it is provided */ @@ -722,7 +806,7 @@ type UIOptionsBaseType = Partial< /** Allows RJSF to override the default widget implementation by specifying either the name of a widget that is used * to look up an implementation from the `widgets` list or an actual one-off widget implementation itself */ - widget?: Widget | string; + widget?: Widget | string; /** When using `additionalProperties`, key collision is prevented by appending a unique integer to the duplicate key. * This option allows you to change the separator between the original key name and the integer. Default is "-" */ @@ -730,7 +814,11 @@ type UIOptionsBaseType = Partial< }; /** The type that represents the Options potentially provided by `ui:options` */ -export type UIOptionsType = UIOptionsBaseType & { +export type UIOptionsType< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +> = UIOptionsBaseType & { /** Anything else will be one of these types */ [key: string]: boolean | number | string | object | any[] | null | undefined; }; @@ -738,16 +826,20 @@ export type UIOptionsType = UIOptionsBaseType & { /** Type describing the well-known properties of the `UiSchema` while also supporting all user defined properties, * starting with `ui:`. */ -export type UiSchema = GenericObjectType & - MakeUIType> & { +export type UiSchema< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +> = GenericObjectType & + MakeUIType> & { /** Allows the form to generate a unique prefix for the `Form`'s root prefix */ "ui:rootFieldId"?: string; /** Allows RJSF to override the default field implementation by specifying either the name of a field that is used * to look up an implementation from the `fields` list or an actual one-off `Field` component implementation itself */ - "ui:field"?: Field | string; - /** An object that contains all of the potential UI options in a single object */ - "ui:options"?: UIOptionsType; + "ui:field"?: Field | string; + /** An object that contains all the potential UI options in a single object */ + "ui:options"?: UIOptionsType; }; /** A `CustomValidator` function takes in a `formData` and `errors` object and returns the given `errors` object back, @@ -776,7 +868,10 @@ export type ValidationData = { /** The interface that describes the validation functions that are provided by a Validator implementation used by the * schema utilities. */ -export interface ValidatorType { +export interface ValidatorType< + T = any, + S extends StrictRJSFSchema = RJSFSchema +> { /** This function processes the `formData` with an optional user contributed `customValidate` function, which receives * the form data and a `errorHandler` function that will be used to add custom validation errors for each field. Also * supports a `transformErrors` function that will take the raw AJV validation errors, prior to custom validation and @@ -788,8 +883,8 @@ export interface ValidatorType { * @param [transformErrors] - An optional function that is used to transform errors after AJV validation */ validateFormData( - formData: T, - schema: RJSFSchema, + formData: T | undefined, + schema: S, customValidate?: CustomValidator, transformErrors?: ErrorTransformer ): ValidationData; @@ -810,7 +905,7 @@ export interface ValidatorType { * @param formData- - The form data to validate * @param rootSchema - The root schema used to provide $ref resolutions */ - isValid(schema: RJSFSchema, formData: T, rootSchema: RJSFSchema): boolean; + isValid(schema: S, formData: T, rootSchema: S): boolean; } /** The `SchemaUtilsType` interface provides a wrapper around the publicly exported APIs in the `@rjsf/utils/schema` @@ -818,12 +913,16 @@ export interface ValidatorType { * the `validator` and `rootSchema` generally does not change across a `Form`, this allows for providing a simplified * set of APIs to the `@rjsf/core` components and the various themes as well. */ -export interface SchemaUtilsType { +export interface SchemaUtilsType< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F = any +> { /** Returns the `ValidatorType` in the `SchemaUtilsType` * * @returns - The `ValidatorType` */ - getValidator(): ValidatorType; + getValidator(): ValidatorType; /** Determines whether either the `validator` and `rootSchema` differ from the ones associated with this instance of * the `SchemaUtilsType`. If either `validator` or `rootSchema` are falsy, then return false to prevent the creation * of a new `SchemaUtilsType` with incomplete properties. @@ -832,10 +931,7 @@ export interface SchemaUtilsType { * @param rootSchema - The root schema that will be compared against the current one * @returns - True if the `SchemaUtilsType` differs from the given `validator` or `rootSchema` */ - doesSchemaUtilsDiffer( - validator: ValidatorType, - rootSchema: RJSFSchema - ): boolean; + doesSchemaUtilsDiffer(validator: ValidatorType, rootSchema: S): boolean; /** Returns the superset of `formData` that includes the given set updated to include any missing fields that have * computed to have defaults provided in the `schema`. * @@ -845,7 +941,7 @@ export interface SchemaUtilsType { * @returns - The resulting `formData` with all the defaults provided */ getDefaultFormState( - schema: RJSFSchema, + schema: S, formData?: T, includeUndefinedValues?: boolean ): T | T[] | undefined; @@ -856,42 +952,38 @@ export interface SchemaUtilsType { * @param [uiSchema] - The UI schema from which to derive potentially displayable information * @returns - True if the label should be displayed or false if it should not */ - getDisplayLabel( - schema: RJSFSchema, - uiSchema?: UiSchema - ): boolean; + getDisplayLabel(schema: S, uiSchema?: UiSchema): boolean; /** Given the `formData` and list of `options`, attempts to find the index of the option that best matches the data. * * @param formData - The current formData, if any, onto which to provide any missing defaults * @param options - The list of options to find a matching options from * @returns - The index of the matched option or 0 if none is available */ - getMatchingOption(formData: T, options: RJSFSchema[]): number; + getMatchingOption(formData: T, options: S[]): number; /** Checks to see if the `schema` and `uiSchema` combination represents an array of files * * @param schema - The schema for which check for array of files flag is desired * @param [uiSchema] - The UI schema from which to check the widget * @returns - True if schema/uiSchema contains an array of files, otherwise false */ - isFilesArray(schema: RJSFSchema, uiSchema?: UiSchema): boolean; + isFilesArray(schema: S, uiSchema?: UiSchema): boolean; /** Checks to see if the `schema` combination represents a multi-select * * @param schema - The schema for which check for a multi-select flag is desired * @returns - True if schema contains a multi-select, otherwise false */ - isMultiSelect(schema: RJSFSchema): boolean; + isMultiSelect(schema: S): boolean; /** Checks to see if the `schema` combination represents a select * * @param schema - The schema for which check for a select flag is desired * @returns - True if schema contains a select, otherwise false */ - isSelect(schema: RJSFSchema): boolean; + isSelect(schema: S): boolean; /** Merges the errors in `additionalErrorSchema` into the existing `validationData` by combining the hierarchies in the * two `ErrorSchema`s and then appending the error list from the `additionalErrorSchema` obtained by calling * `validator.toErrorList()` onto the `errors` in the `validationData`. If no `additionalErrorSchema` is passed, then * `validationData` is returned. * - * @param validator - The validator used to convert an ErrorSchema to a list of errors * @param validationData - The current `ValidationData` into which to merge the additional errors * @param [additionalErrorSchema] - The additional set of errors * @returns - The `validationData` with the additional errors from `additionalErrorSchema` merged into it, if provided. @@ -905,10 +997,10 @@ export interface SchemaUtilsType { * recursive resolution. * * @param schema - The schema for which retrieving a schema is desired - * @param [rawFormData] - The current formData, if any, to assist retrieving a schema + * @param [formData] - The current formData, if any, to assist retrieving a schema * @returns - The schema having its conditions, additional properties, references and dependencies resolved */ - retrieveSchema(schema: RJSFSchema, formData?: T): RJSFSchema; + retrieveSchema(schema: S, formData?: T): S; /** Generates an `IdSchema` object for the `schema`, recursively * * @param schema - The schema for which the display label flag is desired @@ -919,7 +1011,7 @@ export interface SchemaUtilsType { * @returns - The `IdSchema` object for the `schema` */ toIdSchema( - schema: RJSFSchema, + schema: S, id?: string, formData?: T, idPrefix?: string, @@ -932,5 +1024,5 @@ export interface SchemaUtilsType { * @param [formData] - The current formData, if any, onto which to provide any missing defaults * @returns - The `PathSchema` object for the `schema` */ - toPathSchema(schema: RJSFSchema, name?: string, formData?: T): PathSchema; + toPathSchema(schema: S, name?: string, formData?: T): PathSchema; } diff --git a/packages/utils/test/getTemplate.test.ts b/packages/utils/test/getTemplate.test.ts index 69ef9199e8..f503ed546f 100644 --- a/packages/utils/test/getTemplate.test.ts +++ b/packages/utils/test/getTemplate.test.ts @@ -1,6 +1,7 @@ import { createSchemaUtils, getTemplate, + RJSFSchema, Registry, TemplatesType, UIOptionsType, @@ -13,7 +14,7 @@ const CustomTemplate = () => undefined; const registry: Registry = { formContext: {}, - rootSchema: {}, + rootSchema: {} as RJSFSchema, schemaUtils: createSchemaUtils(getTestValidator({}), {}), templates: { ArrayFieldDescriptionTemplate: FakeTemplate, diff --git a/packages/utils/test/schema/retrieveSchemaTest.ts b/packages/utils/test/schema/retrieveSchemaTest.ts index 9ac5787498..eacdfd74b1 100644 --- a/packages/utils/test/schema/retrieveSchemaTest.ts +++ b/packages/utils/test/schema/retrieveSchemaTest.ts @@ -3,7 +3,6 @@ import { RJSFSchema, createSchemaUtils, ADDITIONAL_PROPERTY_FLAG, - RJSFSchemaDefinition, } from "../../src"; import { resolveSchema, @@ -1433,7 +1432,7 @@ export default function retrieveSchemaTest(testValidator: TestValidatorType) { const schema: RJSFSchema = { type: "integer", }; - const oneOf: RJSFSchemaDefinition[] = [ + const oneOf: RJSFSchema["oneOf"] = [ true, { properties: undefined }, { properties: { foo: { type: "string" } } }, diff --git a/packages/validator-ajv6/src/validator.ts b/packages/validator-ajv6/src/validator.ts index c74d9d1f34..1fa97fe132 100644 --- a/packages/validator-ajv6/src/validator.ts +++ b/packages/validator-ajv6/src/validator.ts @@ -229,7 +229,7 @@ export default class AJV6Validator implements ValidatorType { * @param [transformErrors] - An optional function that is used to transform errors after AJV validation */ validateFormData( - formData: T, + formData: T | undefined, schema: RJSFSchema, customValidate?: CustomValidator, transformErrors?: ErrorTransformer @@ -324,7 +324,7 @@ export default class AJV6Validator implements ValidatorType { /** Takes a `node` object list and transforms any contained `$ref` node variables with a prefix, recursively calling * `withIdRefPrefix` for any other elements. * - * @param nodeThe - list of object nodes to which a ROOT_SCHEMA_PREFIX is added when a REF_KEY is part of it + * @param node - The list of object nodes to which a ROOT_SCHEMA_PREFIX is added when a REF_KEY is part of it * @private */ private withIdRefPrefixArray(node: object[]): RJSFSchema { diff --git a/packages/validator-ajv6/test/validator.test.ts b/packages/validator-ajv6/test/validator.test.ts index 3031975da3..b80a548136 100644 --- a/packages/validator-ajv6/test/validator.test.ts +++ b/packages/validator-ajv6/test/validator.test.ts @@ -47,7 +47,7 @@ describe("AJV6Validator", () => { expect(validator.isValid(schema, { foo: 12345 }, schema)).toBe(false); }); it("should return false if the schema is invalid", () => { - const schema: RJSFSchema = "foobarbaz" as RJSFSchema; + const schema: RJSFSchema = "foobarbaz" as unknown as RJSFSchema; expect(validator.isValid(schema, { foo: "bar" }, schema)).toBe(false); }); diff --git a/packages/validator-ajv8/src/customizeValidator.ts b/packages/validator-ajv8/src/customizeValidator.ts index e7f5ebe7d2..0c575bc754 100644 --- a/packages/validator-ajv8/src/customizeValidator.ts +++ b/packages/validator-ajv8/src/customizeValidator.ts @@ -1,4 +1,4 @@ -import { ValidatorType } from "@rjsf/utils"; +import { RJSFSchema, StrictRJSFSchema, ValidatorType } from "@rjsf/utils"; import { CustomValidatorOptionsType, Localizer } from "./types"; import AJV8Validator from "./validator"; @@ -9,9 +9,12 @@ import AJV8Validator from "./validator"; * @param [options={}] - The `CustomValidatorOptionsType` options that are used to create the `ValidatorType` instance * @param [localizer] - If provided, is used to localize a list of Ajv `ErrorObject`s */ -export default function customizeValidator( +export default function customizeValidator< + T = any, + S extends StrictRJSFSchema = RJSFSchema +>( options: CustomValidatorOptionsType = {}, localizer?: Localizer -): ValidatorType { - return new AJV8Validator(options, localizer); +): ValidatorType { + return new AJV8Validator(options, localizer); } diff --git a/packages/validator-ajv8/src/validator.ts b/packages/validator-ajv8/src/validator.ts index e7c619a192..f559ac657c 100644 --- a/packages/validator-ajv8/src/validator.ts +++ b/packages/validator-ajv8/src/validator.ts @@ -1,5 +1,7 @@ import Ajv, { ErrorObject } from "ajv"; import toPath from "lodash/toPath"; +import isObject from "lodash/isObject"; +import clone from "lodash/clone"; import { CustomValidator, ErrorSchema, @@ -9,10 +11,10 @@ import { GenericObjectType, RJSFSchema, RJSFValidationError, + StrictRJSFSchema, ValidationData, ValidatorType, getDefaultFormState, - isObject, mergeValidationData, ERRORS_KEY, REF_KEY, @@ -25,7 +27,11 @@ const ROOT_SCHEMA_PREFIX = "__rjsf_rootSchema"; /** `ValidatorType` implementation that uses the AJV 8 validation mechanism. */ -export default class AJV8Validator implements ValidatorType { +export default class AJV8Validator< + T = any, + S extends StrictRJSFSchema = RJSFSchema +> implements ValidatorType +{ /** The AJV instance to use for all validations * * @private @@ -167,17 +173,17 @@ export default class AJV8Validator implements ValidatorType { this.__errors!.push(message); }, }; + if (Array.isArray(formData)) { + return formData.reduce((acc, value, key) => { + return { ...acc, [key]: this.createErrorHandler(value) }; + }, handler); + } if (isObject(formData)) { const formObject: GenericObjectType = formData as GenericObjectType; return Object.keys(formObject).reduce((acc, key) => { return { ...acc, [key]: this.createErrorHandler(formObject[key]) }; }, handler as FormValidation); } - if (Array.isArray(formData)) { - return formData.reduce((acc, value, key) => { - return { ...acc, [key]: this.createErrorHandler(value) }; - }, handler); - } return handler as FormValidation; } @@ -242,8 +248,8 @@ export default class AJV8Validator implements ValidatorType { * @param [transformErrors] - An optional function that is used to transform errors after AJV validation */ validateFormData( - formData: T, - schema: RJSFSchema, + formData: T | undefined, + schema: S, customValidate?: CustomValidator, transformErrors?: ErrorTransformer ): ValidationData { @@ -320,9 +326,9 @@ export default class AJV8Validator implements ValidatorType { * @param node - The object node to which a ROOT_SCHEMA_PREFIX is added when a REF_KEY is part of it * @private */ - private withIdRefPrefixObject(node: object) { + private withIdRefPrefixObject(node: S) { for (const key in node) { - const realObj: { [k: string]: any } = node; + const realObj: GenericObjectType = node; const value = realObj[key]; if ( key === REF_KEY && @@ -340,14 +346,14 @@ export default class AJV8Validator implements ValidatorType { /** Takes a `node` object list and transforms any contained `$ref` node variables with a prefix, recursively calling * `withIdRefPrefix` for any other elements. * - * @param nodeThe - list of object nodes to which a ROOT_SCHEMA_PREFIX is added when a REF_KEY is part of it + * @param node - The list of object nodes to which a ROOT_SCHEMA_PREFIX is added when a REF_KEY is part of it * @private */ - private withIdRefPrefixArray(node: object[]): RJSFSchema { + private withIdRefPrefixArray(node: S[]): S[] { for (let i = 0; i < node.length; i++) { - node[i] = this.withIdRefPrefix(node[i]); + node[i] = this.withIdRefPrefix(node[i]) as S; } - return node as RJSFSchema; + return node; } /** Validates data against a schema, returning true if the data is valid, or @@ -358,7 +364,7 @@ export default class AJV8Validator implements ValidatorType { * @param formData- - The form data to validate * @param rootSchema - The root schema used to provide $ref resolutions */ - isValid(schema: RJSFSchema, formData: T, rootSchema: RJSFSchema) { + isValid(schema: S, formData: T, rootSchema: S) { try { // add the rootSchema ROOT_SCHEMA_PREFIX as id. // then rewrite the schema ref's to point to the rootSchema @@ -382,13 +388,13 @@ export default class AJV8Validator implements ValidatorType { * @param schemaNode - The object node to which a ROOT_SCHEMA_PREFIX is added when a REF_KEY is part of it * @protected */ - protected withIdRefPrefix(schemaNode: RJSFSchema): RJSFSchema { - if (schemaNode.constructor === Object) { - return this.withIdRefPrefixObject({ ...schemaNode }); - } + protected withIdRefPrefix(schemaNode: S | S[]): S | S[] { if (Array.isArray(schemaNode)) { return this.withIdRefPrefixArray([...schemaNode]); } + if (isObject(schemaNode)) { + return this.withIdRefPrefixObject(clone(schemaNode)); + } return schemaNode; } } diff --git a/packages/validator-ajv8/test/validator.test.ts b/packages/validator-ajv8/test/validator.test.ts index 8edeae1e81..aeb4120dee 100644 --- a/packages/validator-ajv8/test/validator.test.ts +++ b/packages/validator-ajv8/test/validator.test.ts @@ -47,7 +47,7 @@ describe("AJV8Validator", () => { expect(validator.isValid(schema, { foo: 12345 }, schema)).toBe(false); }); it("should return false if the schema is invalid", () => { - const schema: RJSFSchema = "foobarbaz" as RJSFSchema; + const schema: RJSFSchema = "foobarbaz" as unknown as RJSFSchema; expect(validator.isValid(schema, { foo: "bar" }, schema)).toBe(false); });