From fea0336d578fcda051e7bc9a3568da535a1be0ff Mon Sep 17 00:00:00 2001 From: "LFRW2K\\lecmil2" Date: Mon, 28 Oct 2024 10:46:09 +0100 Subject: [PATCH 01/13] [MISC] Retrieved change from older PR --- CHANGELOG.md | 9 +++++++++ .../advanced-customization/custom-templates.md | 10 +++++----- packages/utils/src/getTemplate.ts | 11 +++++++++++ packages/utils/test/getTemplate.test.ts | 15 +++++++++++++++ 4 files changed, 40 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0735abc61f..bb99b8af92 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,15 @@ it according to semantic versioning. For example, if your PR adds a breaking cha should change the heading of the (upcoming) version to include a major version bump. --> +# 5.23.0 + +## @rjsf/utils + +- Updated `getTemplate()` to allow per-field customization using string key from `Registry`. + +## Dev / docs / playground + +- Updated `advanced-customization/custom-templates` with the new feature. # 5.22.0 diff --git a/packages/docs/docs/advanced-customization/custom-templates.md b/packages/docs/docs/advanced-customization/custom-templates.md index 6491efbb5c..520646340b 100644 --- a/packages/docs/docs/advanced-customization/custom-templates.md +++ b/packages/docs/docs/advanced-customization/custom-templates.md @@ -76,7 +76,7 @@ render( ); ``` -You also can provide your own field template to a uiSchema by specifying a `ui:ArrayFieldTemplate` property. +You also can provide your own field template to a uiSchema by specifying a `ui:ArrayFieldTemplate` property with your Component or a string value from the `Registry`. ```tsx import { UiSchema } from '@rjsf/utils'; @@ -163,7 +163,7 @@ render( ); ``` -You also can provide your own template to a uiSchema by specifying a `ui:ArrayFieldDescriptionTemplate` property. +You also can provide your own template to a uiSchema by specifying a `ui:ArrayFieldDescriptionTemplate` property with your Component or a string value from the `Registry`. ```tsx import { UiSchema } from '@rjsf/utils'; @@ -261,7 +261,7 @@ render( ); ``` -You also can provide your own template to a uiSchema by specifying a `ui:ArrayFieldDescriptionTemplate` property. +You also can provide your own template to a uiSchema by specifying a `ui:ArrayFieldDescriptionTemplate` property with your Component or a string value from the `Registry`. ```tsx import { UiSchema } from '@rjsf/utils'; @@ -615,7 +615,7 @@ render( ); ``` -You also can provide your own field template to a uiSchema by specifying a `ui:FieldTemplate` property. +You also can provide your own field template to a uiSchema by specifying a `ui:FieldTemplate` property with your Component or a string value from the `Registry`. ```tsx import { UiSchema } from '@rjsf/utils'; @@ -693,7 +693,7 @@ render( ); ``` -You also can provide your own field template to a uiSchema by specifying a `ui:ObjectFieldTemplate` property. +You also can provide your own field template to a uiSchema by specifying a `ui:ObjectFieldTemplate` property with your Component or a string value from the `Registry`. ```tsx import { UiSchema } from '@rjsf/utils'; diff --git a/packages/utils/src/getTemplate.ts b/packages/utils/src/getTemplate.ts index 8863df65d5..ca9733d53f 100644 --- a/packages/utils/src/getTemplate.ts +++ b/packages/utils/src/getTemplate.ts @@ -18,6 +18,17 @@ export default function getTemplate< if (name === 'ButtonTemplates') { return templates[name]; } + // Allow templates to be customized per-field by using string keys from the registry + if ( + Object.hasOwn(uiOptions, name) && + typeof uiOptions[name] === 'string' && + Object.hasOwn(templates, uiOptions[name] as string) + ) { + const key = uiOptions[name]; + // Evaluating templates[key] results in TS2590: Expression produces a union type that is too complex to represent + // To avoid that, we cast templates to `any` before accessing the key field + return (templates as any)[key]; + } 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 diff --git a/packages/utils/test/getTemplate.test.ts b/packages/utils/test/getTemplate.test.ts index 7b9300475f..48737fdaa2 100644 --- a/packages/utils/test/getTemplate.test.ts +++ b/packages/utils/test/getTemplate.test.ts @@ -86,4 +86,19 @@ describe('getTemplate', () => { expect(getTemplate(name, registry, uiOptions)).toBe(CustomTemplate); }); }); + it('returns the template from registry using uiOptions key when available', () => { + KEYS.forEach((key) => { + const name = key as keyof TemplatesType; + expect( + getTemplate( + name, + registry, + Object.keys(uiOptions).reduce((uiOptions, key) => { + uiOptions[key] = key; + return uiOptions; + }, {}) + ) + ).toBe(FakeTemplate); + }); + }); }); From 4fc99d149d4fa250248da48654eeed4f4d20de63 Mon Sep 17 00:00:00 2001 From: "LFRW2K\\lecmil2" Date: Mon, 28 Oct 2024 11:08:48 +0100 Subject: [PATCH 02/13] [MISC] Updated documentation --- .../custom-templates.md | 54 +++++++++++++++++-- 1 file changed, 49 insertions(+), 5 deletions(-) diff --git a/packages/docs/docs/advanced-customization/custom-templates.md b/packages/docs/docs/advanced-customization/custom-templates.md index 520646340b..f669555a71 100644 --- a/packages/docs/docs/advanced-customization/custom-templates.md +++ b/packages/docs/docs/advanced-customization/custom-templates.md @@ -76,16 +76,27 @@ render( ); ``` -You also can provide your own field template to a uiSchema by specifying a `ui:ArrayFieldTemplate` property with your Component or a string value from the `Registry`. +You also can provide your own field template to a uiSchema by specifying a `ui:ArrayFieldTemplate` property with your Component : ```tsx import { UiSchema } from '@rjsf/utils'; +import ArrayFieldTemplate from './ArrayFieldTemplate'; const uiSchema: UiSchema = { 'ui:ArrayFieldTemplate': ArrayFieldTemplate, }; ``` +or a string value from the `Registry` : + +```tsx +import { UiSchema } from '@rjsf/utils'; + +const uiSchema: UiSchema = { + 'ui:ArrayFieldTemplate': 'CustomArrayFieldTemplate', +}; +``` + Please see the [customArray.tsx sample](https://github.com/rjsf-team/react-jsonschema-form/blob/main/packages/playground/src/samples/customArray.tsx) from the [playground](https://rjsf-team.github.io/react-jsonschema-form/) for another example. The following props are passed to each `ArrayFieldTemplate`: @@ -163,16 +174,27 @@ render( ); ``` -You also can provide your own template to a uiSchema by specifying a `ui:ArrayFieldDescriptionTemplate` property with your Component or a string value from the `Registry`. +You also can provide your own field template to a uiSchema by specifying a `ui:ArrayFieldDescriptionTemplate` property with your Component : ```tsx import { UiSchema } from '@rjsf/utils'; +import ArrayFieldDescriptionTemplate from './ArrayFieldDescriptionTemplate'; const uiSchema: UiSchema = { 'ui:ArrayFieldDescriptionTemplate': ArrayFieldDescriptionTemplate, }; ``` +or a string value from the `Registry` : + +```tsx +import { UiSchema } from '@rjsf/utils'; + +const uiSchema: UiSchema = { + 'ui:ArrayFieldDescriptionTemplate': 'CustomArrayFieldDescriptionTemplate', +}; +``` + The following props are passed to each `ArrayFieldDescriptionTemplate`: - `description`: The description of the array field being rendered. @@ -615,13 +637,24 @@ render( ); ``` -You also can provide your own field template to a uiSchema by specifying a `ui:FieldTemplate` property with your Component or a string value from the `Registry`. +You also can provide your own field template to a uiSchema by specifying a `ui:FieldTemplate` property with your Component : ```tsx import { UiSchema } from '@rjsf/utils'; +import FieldTemplate from './FieldTemplate'; const uiSchema: UiSchema = { - 'ui:FieldTemplate': CustomFieldTemplate, + 'ui:FieldTemplate': FieldTemplate, +}; +``` + +or a string value from the `Registry` : + +```tsx +import { UiSchema } from '@rjsf/utils'; + +const uiSchema: UiSchema = { + 'ui:FieldTemplate': 'CustomFieldTemplate', }; ``` @@ -693,16 +726,27 @@ render( ); ``` -You also can provide your own field template to a uiSchema by specifying a `ui:ObjectFieldTemplate` property with your Component or a string value from the `Registry`. +You also can provide your own field template to a uiSchema by specifying a `ui:ObjectFieldTemplate` property with your Component : ```tsx import { UiSchema } from '@rjsf/utils'; +import ObjectFieldTemplate from './ObjectFieldTemplate'; const uiSchema: UiSchema = { 'ui:ObjectFieldTemplate': ObjectFieldTemplate, }; ``` +or a string value from the `Registry` : + +```tsx +import { UiSchema } from '@rjsf/utils'; + +const uiSchema: UiSchema = { + 'ui:ObjectFieldTemplate': 'ObjectFieldTemplate', +}; +``` + Please see the [customObject.tsx sample](https://github.com/rjsf-team/react-jsonschema-form/blob/main/packages/playground/src/samples/customObject.tsx) from the [playground](https://rjsf-team.github.io/react-jsonschema-form/) for a better example. The following props are passed to each `ObjectFieldTemplate` as defined by the `ObjectFieldTemplateProps` in `@rjsf/utils`: From 8640c609d0b01b7176d2447cb3058f4520132f67 Mon Sep 17 00:00:00 2001 From: "LFRW2K\\lecmil2" Date: Mon, 28 Oct 2024 13:36:07 +0100 Subject: [PATCH 03/13] [MISC] Updated types --- packages/utils/src/types.ts | 183 ++++++++++++++++-------------------- 1 file changed, 81 insertions(+), 102 deletions(-) diff --git a/packages/utils/src/types.ts b/packages/utils/src/types.ts index 0b6c400504..7755f58b4c 100644 --- a/packages/utils/src/types.ts +++ b/packages/utils/src/types.ts @@ -223,51 +223,45 @@ 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 extends FormContextType = any +> = RJSFBaseProps & { /** The errorSchema constructed by `Form` */ errorSchema: ErrorSchema; /** An array of the errors */ errors: RJSFValidationError[]; /** The `formContext` object that was passed to `Form` */ formContext?: F; - /** The schema that was passed to `Form` */ - schema: S; - /** The uiSchema that was passed to `Form` */ - uiSchema?: UiSchema; - /** The `registry` object */ - registry: Registry; }; /** The properties that are passed to an `FieldErrorTemplate` implementation */ -export type FieldErrorProps = { +export type FieldErrorProps< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +> = RJSFBaseProps & { /** The errorSchema constructed by `Form` */ errorSchema?: ErrorSchema; /** An array of the errors */ errors?: Array; /** The tree of unique ids for every child field */ idSchema: IdSchema; - /** The schema that was passed to field */ - schema: S; - /** The uiSchema that was passed to field */ - uiSchema?: UiSchema; - /** The `registry` object */ - registry: Registry; }; /** The properties that are passed to an `FieldHelpTemplate` implementation */ -export type FieldHelpProps = { +export type FieldHelpProps< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +> = RJSFBaseProps & { /** The help information to be rendered */ help?: string | ReactElement; /** The tree of unique ids for every child field */ idSchema: IdSchema; - /** The schema that was passed to field */ - schema: S; - /** The uiSchema that was passed to field */ - uiSchema?: UiSchema; /** Flag indicating whether there are errors associated with this field */ hasErrors?: boolean; - /** The `registry` object */ - registry: Registry; }; /** The set of `Fields` stored in the `Registry` */ @@ -282,8 +276,17 @@ export type RegistryWidgetsType; }; +export type RJSFBaseProps = { + /** The schema object for the field being described */ + schema: S; + /** The uiSchema object for this description field */ + uiSchema?: UiSchema; + /** The `registry` object */ + registry: Registry; +}; + /** The set of RJSF templates that can be overridden by themes or users */ -export interface TemplatesType { +export type TemplatesType = { /** The template to use while rendering normal or fixed array fields */ ArrayFieldTemplate: ComponentType>; /** The template to use while rendering the description for an array field */ @@ -327,7 +330,10 @@ export interface TemplatesType>; }; -} +} & { + /** Allow this to support any named `ComponentType` or an object of named `ComponentType`s */ + [key: string]: ComponentType> | { [key: string]: ComponentType> }; +}; /** The set of UiSchema options that can be set globally and used as fallbacks at an individual template, field or * widget level when no field-level value of the option is provided. @@ -433,7 +439,11 @@ export type Field; /** The properties that are passed to a FieldTemplate implementation */ -export type FieldTemplateProps = { +export type FieldTemplateProps< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +> = RJSFBaseProps & { /** 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 */ @@ -474,10 +484,6 @@ export type FieldTemplateProps; /** The `formContext` object that was passed to `Form` */ formContext?: F; /** The formData for this field */ @@ -488,50 +494,44 @@ export type FieldTemplateProps () => void; /** 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; }; /** The properties that are passed to the `UnsupportedFieldTemplate` implementation */ -export type UnsupportedFieldProps = { - /** The schema object for this field */ - schema: S; +export type UnsupportedFieldProps< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +> = RJSFBaseProps & { /** 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; }; /** The properties that are passed to a `TitleFieldTemplate` implementation */ -export type TitleFieldProps = { +export type TitleFieldProps< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +> = RJSFBaseProps & { /** 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: S; - /** The uiSchema object for this title field */ - uiSchema?: UiSchema; /** A boolean value stating if the field is required */ required?: boolean; - /** The `registry` object */ - registry: Registry; }; /** The properties that are passed to a `DescriptionFieldTemplate` implementation */ -export type DescriptionFieldProps = { +export type DescriptionFieldProps< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +> = RJSFBaseProps & { /** The id of the field description in the hierarchy */ id: string; - /** The schema object for the field being described */ - schema: S; - /** The uiSchema object for this description field */ - uiSchema?: UiSchema; /** The description of the field being rendered */ description: string | ReactElement; - /** The `registry` object */ - registry: Registry; }; /** The properties that are passed to a `ArrayFieldTitleTemplate` implementation */ @@ -563,7 +563,7 @@ export type ArrayFieldTemplateItemType< T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any -> = { +> = RJSFBaseProps & { /** The html for the item's content */ children: ReactElement; /** The className string */ @@ -598,12 +598,6 @@ export type ArrayFieldTemplateItemType< readonly?: boolean; /** A stable, unique key for the array item */ key: string; - /** The schema object for this array item */ - schema: S; - /** The uiSchema object for this array item */ - uiSchema?: UiSchema; - /** The `registry` object */ - registry: Registry; }; /** The properties that are passed to an ArrayFieldTemplate implementation */ @@ -611,7 +605,7 @@ export type ArrayFieldTemplateProps< T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any -> = { +> = RJSFBaseProps & { /** A boolean value stating whether new elements can be added to the array */ canAdd?: boolean; /** The className string */ @@ -630,10 +624,6 @@ export type ArrayFieldTemplateProps< required?: boolean; /** A boolean value stating if the field is hiding its errors */ hideError?: boolean; - /** The schema object for this array */ - schema: S; - /** The uiSchema object for this array field */ - uiSchema?: UiSchema; /** A string value containing the title for the array */ title: string; /** The `formContext` object that was passed to Form */ @@ -644,8 +634,6 @@ export type ArrayFieldTemplateProps< errorSchema?: ErrorSchema; /** An array of strings listing all generated error messages from encountered errors for this widget */ rawErrors?: string[]; - /** The `registry` object */ - registry: Registry; }; /** The properties of each element in the ObjectFieldTemplateProps.properties array */ @@ -667,7 +655,7 @@ export type ObjectFieldTemplateProps< T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any -> = { +> = RJSFBaseProps & { /** A string value containing the title for the object */ title: string; /** A string value containing the description for the object */ @@ -684,10 +672,6 @@ export type ObjectFieldTemplateProps< required?: boolean; /** A boolean value stating if the field is hiding its errors */ hideError?: boolean; - /** The schema object for this object */ - schema: S; - /** The uiSchema object for this object field */ - uiSchema?: UiSchema; /** An object containing the id for this object & ids for its properties */ idSchema: IdSchema; /** The optional validation errors in the form of an `ErrorSchema` */ @@ -696,8 +680,6 @@ export type ObjectFieldTemplateProps< formData?: T; /** The `formContext` object that was passed to Form */ formContext?: F; - /** The `registry` object */ - registry: Registry; }; /** The properties that are passed to a WrapIfAdditionalTemplate implementation */ @@ -705,24 +687,24 @@ export type WrapIfAdditionalTemplateProps< T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any -> = { +> = RJSFBaseProps & { /** The field or widget component instance for this field row */ children: ReactNode; } & Pick< - FieldTemplateProps, - | 'id' - | 'classNames' - | 'style' - | 'label' - | 'required' - | 'readonly' - | 'disabled' - | 'schema' - | 'uiSchema' - | 'onKeyChange' - | 'onDropPropertyClick' - | 'registry' ->; + FieldTemplateProps, + | 'id' + | 'classNames' + | 'style' + | 'label' + | 'required' + | 'readonly' + | 'disabled' + | 'schema' + | 'uiSchema' + | 'onKeyChange' + | 'onDropPropertyClick' + | 'registry' + >; /** The properties that are passed to a Widget implementation */ export interface WidgetProps @@ -793,7 +775,8 @@ export interface BaseInputTemplateProps< T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any -> extends WidgetProps { +> extends WidgetProps, + RJSFBaseProps { /** A `BaseInputTemplate` implements a default `onChange` handler that it passes to the HTML input component to handle * the `ChangeEvent`. Sometimes a widget may need to handle the `ChangeEvent` using custom logic. If that is the case, * that widget should provide its own handler via this prop. @@ -802,28 +785,24 @@ export interface BaseInputTemplateProps< } /** The type that defines the props used by the Submit button */ -export type SubmitButtonProps = { - /** The uiSchema for this widget */ - uiSchema?: UiSchema; - /** The `registry` object */ - registry: Registry; -}; +export type SubmitButtonProps< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +> = RJSFBaseProps; /** 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 extends FormContextType = any -> = ButtonHTMLAttributes & { - /** An alternative specification for the type of the icon button */ - iconType?: string; - /** The name representation or actual react element implementation for the icon */ - icon?: string | ReactElement; - /** The uiSchema for this widget */ - uiSchema?: UiSchema; - /** The `registry` object */ - registry: Registry; -}; +> = ButtonHTMLAttributes & + RJSFBaseProps & { + /** An alternative specification for the type of the icon button */ + iconType?: string; + /** The name representation or actual react element implementation for the icon */ + icon?: string | ReactElement; + }; /** The type that defines how to change the behavior of the submit button for the form */ export type UISchemaSubmitButtonOptions = { From 15e43ff04b79a05cc9eb57663cbbdae70a8bc48c Mon Sep 17 00:00:00 2001 From: "LFRW2K\\lecmil2" Date: Mon, 28 Oct 2024 13:55:38 +0100 Subject: [PATCH 04/13] [EVO] Added test for custom template name in registry --- packages/utils/test/getTemplate.test.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/utils/test/getTemplate.test.ts b/packages/utils/test/getTemplate.test.ts index 48737fdaa2..231aa3bc0e 100644 --- a/packages/utils/test/getTemplate.test.ts +++ b/packages/utils/test/getTemplate.test.ts @@ -94,11 +94,23 @@ describe('getTemplate', () => { name, registry, Object.keys(uiOptions).reduce((uiOptions, key) => { - uiOptions[key] = key; + (uiOptions as Record)[key] = key; return uiOptions; }, {}) ) ).toBe(FakeTemplate); }); }); + it('returns the custom template name from the registry', () => { + const customTemplateKey = 'CustomTemplate'; + registry.templates[customTemplateKey] = FakeTemplate; + + expect(getTemplate(customTemplateKey, registry)).toBe(FakeTemplate); + }); + + it('returns undefined when the custom template is not in the registry', () => { + const customTemplateKey = 'CustomTemplate'; + + expect(getTemplate(customTemplateKey, registry)).toBeUndefined(); + }); }); From 485cce735cf2530acbcd7b489a3e486ed056101e Mon Sep 17 00:00:00 2001 From: "LFRW2K\\lecmil2" Date: Mon, 28 Oct 2024 14:01:33 +0100 Subject: [PATCH 05/13] [FIX] Docx --- .../custom-templates.md | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/packages/docs/docs/advanced-customization/custom-templates.md b/packages/docs/docs/advanced-customization/custom-templates.md index f669555a71..7872050c57 100644 --- a/packages/docs/docs/advanced-customization/custom-templates.md +++ b/packages/docs/docs/advanced-customization/custom-templates.md @@ -283,13 +283,24 @@ render( ); ``` -You also can provide your own template to a uiSchema by specifying a `ui:ArrayFieldDescriptionTemplate` property with your Component or a string value from the `Registry`. +You also can provide your own template to a uiSchema by specifying a `ui:ArrayFieldDescriptionTemplate` property with your Component : + +```tsx +import { UiSchema } from '@rjsf/utils'; +import ArrayFieldDescriptionTemplate from './ArrayFieldDescriptionTemplate'; + +const uiSchema: UiSchema = { + 'ui:ArrayFieldDescriptionTemplate': ArrayFieldDescriptionTemplate, +}; +``` + +or a string value from the `Registry` : ```tsx import { UiSchema } from '@rjsf/utils'; const uiSchema: UiSchema = { - 'ui:ArrayFieldTitleTemplate': ArrayFieldTitleTemplate, + 'ui:ArrayFieldDescriptionTemplate': 'CustomArrayFieldDescriptionTemplate', }; ``` @@ -641,10 +652,10 @@ You also can provide your own field template to a uiSchema by specifying a `ui:F ```tsx import { UiSchema } from '@rjsf/utils'; -import FieldTemplate from './FieldTemplate'; +import CustomFieldTemplate from './CustomFieldTemplate'; const uiSchema: UiSchema = { - 'ui:FieldTemplate': FieldTemplate, + 'ui:FieldTemplate': CustomFieldTemplate, }; ``` From 0c1839b1ce878e08798d0fc6faf5ba8bece00ae6 Mon Sep 17 00:00:00 2001 From: "LFRW2K\\lecmil2" Date: Mon, 28 Oct 2024 15:24:22 +0100 Subject: [PATCH 06/13] [FIX] Templates typing --- packages/utils/src/types.ts | 51 +++++++++++++++++++++++++++++++------ 1 file changed, 43 insertions(+), 8 deletions(-) diff --git a/packages/utils/src/types.ts b/packages/utils/src/types.ts index 7755f58b4c..ae3a735377 100644 --- a/packages/utils/src/types.ts +++ b/packages/utils/src/types.ts @@ -332,7 +332,7 @@ export type TemplatesType> | { [key: string]: ComponentType> }; + [key: string]: ComponentType | { [key: string]: ComponentType }; }; /** The set of UiSchema options that can be set globally and used as fallbacks at an individual template, field or @@ -785,11 +785,12 @@ export interface BaseInputTemplateProps< } /** The type that defines the props used by the Submit button */ -export type SubmitButtonProps< - T = any, - S extends StrictRJSFSchema = RJSFSchema, - F extends FormContextType = any -> = RJSFBaseProps; +export type SubmitButtonProps = { + /** The uiSchema for this widget */ + uiSchema?: UiSchema; + /** The `registry` object */ + registry: Registry; +}; /** The type that defines the props for an Icon button, extending from a basic HTML button attributes */ export type IconButtonProps< @@ -797,7 +798,7 @@ export type IconButtonProps< S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any > = ButtonHTMLAttributes & - RJSFBaseProps & { + Omit, 'schema'> & { /** An alternative specification for the type of the icon button */ iconType?: string; /** The name representation or actual react element implementation for the icon */ @@ -838,7 +839,23 @@ type MakeUIType = { * remap the keys. It also contains all the properties, optionally, of `TemplatesType` except "ButtonTemplates" */ type UIOptionsBaseType = Partial< - Omit, 'ButtonTemplates'> + Pick< + TemplatesType, + | 'ArrayFieldDescriptionTemplate' + | 'ArrayFieldItemTemplate' + | 'ArrayFieldTemplate' + | 'ArrayFieldTitleTemplate' + | 'BaseInputTemplate' + | 'DescriptionFieldTemplate' + | 'ErrorListTemplate' + | 'FieldErrorTemplate' + | 'FieldHelpTemplate' + | 'FieldTemplate' + | 'ObjectFieldTemplate' + | 'TitleFieldTemplate' + | 'UnsupportedFieldTemplate' + | 'WrapIfAdditionalTemplate' + > > & GlobalUISchemaOptions & { /** Any classnames that the user wants to be applied to a field in the ui */ @@ -977,6 +994,7 @@ export interface ValidatorType, uiSchema?: UiSchema ): ValidationData; + /** Converts an `errorSchema` into a list of `RJSFValidationErrors` * * @param errorSchema - The `ErrorSchema` instance to convert @@ -985,6 +1003,7 @@ export interface ValidatorType, fieldPath?: string[]): RJSFValidationError[]; + /** Validates data against a schema, returning true if the data is valid, or * false otherwise. If the schema is invalid, then this function will return * false. @@ -994,6 +1013,7 @@ export interface ValidatorType(schema: S, formData?: T): { errors?: Result[]; validationError?: Error }; + /** An optional function that can be used to reset validator implementation. Useful for clear schemas in the AJV * instance for tests. */ @@ -1018,6 +1039,7 @@ export interface SchemaUtilsType; + /** 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. @@ -1034,6 +1056,7 @@ export interface SchemaUtilsType ): 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`. * @@ -1049,6 +1072,7 @@ export interface SchemaUtilsType, globalOptions?: GlobalUISchemaOptions): boolean; + /** Determines which of the given `options` provided most closely matches the `formData`. * Returns the index of the option that is valid and is the closest match, or 0 if there is no match. * @@ -1077,6 +1102,7 @@ export interface SchemaUtilsType): 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: 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: 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 @@ -1129,6 +1160,7 @@ export interface SchemaUtilsType, additionalErrorSchema?: ErrorSchema): ValidationData; + /** Retrieves an expanded schema that has had all of its conditions, additional properties, references and * dependencies resolved and merged into the `schema` given a `rawFormData` that is used to do the potentially * recursive resolution. @@ -1138,6 +1170,7 @@ export interface SchemaUtilsType; + /** Generates an `PathSchema` object for the `schema`, recursively * * @param schema - The schema for which the display label flag is desired From c772a6b8df4f6208afeb31ca551389c3df2e4f82 Mon Sep 17 00:00:00 2001 From: "LFRW2K\\lecmil2" Date: Mon, 28 Oct 2024 15:27:57 +0100 Subject: [PATCH 07/13] [MISC] Updated docs --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bb99b8af92..9878e7de68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,9 @@ should change the heading of the (upcoming) version to include a major version b ## @rjsf/utils -- Updated `getTemplate()` to allow per-field customization using string key from `Registry`. +- Updated `getTemplate()` to allow per-field customization using string key from `Registry`, fixing [#3695](https://github.com/rjsf-team/react-jsonschema-form/issues/3695). +- Updated `TemplatesType` to allow for a string key to be used to reference a custom template in the `Registry`, fixing [#3695](https://github.com/rjsf-team/react-jsonschema-form/issues/3695) +- Updated tests to cover the new `getTemplate()` functionality ## Dev / docs / playground From 7777848e1178f8085c31cae45de498630b9b3ebe Mon Sep 17 00:00:00 2001 From: "LFRW2K\\lecmil2" Date: Mon, 4 Nov 2024 08:59:40 +0100 Subject: [PATCH 08/13] [MISC] Moved type and removed blank lines --- packages/utils/src/types.ts | 37 ++++++++++--------------------------- 1 file changed, 10 insertions(+), 27 deletions(-) diff --git a/packages/utils/src/types.ts b/packages/utils/src/types.ts index ae3a735377..ab39f922ad 100644 --- a/packages/utils/src/types.ts +++ b/packages/utils/src/types.ts @@ -222,6 +222,16 @@ export type FormValidation = FieldValidation & { [key in keyof T]?: FormValidation; }; +/** The base properties passed to various RJSF components. */ +export type RJSFBaseProps = { + /** The schema object for the field being described */ + schema: S; + /** The uiSchema object for this description field */ + uiSchema?: UiSchema; + /** The `registry` object */ + registry: Registry; +}; + /** The properties that are passed to an `ErrorListTemplate` implementation */ export type ErrorListProps< T = any, @@ -276,15 +286,6 @@ export type RegistryWidgetsType; }; -export type RJSFBaseProps = { - /** The schema object for the field being described */ - schema: S; - /** The uiSchema object for this description field */ - uiSchema?: UiSchema; - /** The `registry` object */ - registry: Registry; -}; - /** The set of RJSF templates that can be overridden by themes or users */ export type TemplatesType = { /** The template to use while rendering normal or fixed array fields */ @@ -994,7 +995,6 @@ export interface ValidatorType, uiSchema?: UiSchema ): ValidationData; - /** Converts an `errorSchema` into a list of `RJSFValidationErrors` * * @param errorSchema - The `ErrorSchema` instance to convert @@ -1003,7 +1003,6 @@ export interface ValidatorType, fieldPath?: string[]): RJSFValidationError[]; - /** Validates data against a schema, returning true if the data is valid, or * false otherwise. If the schema is invalid, then this function will return * false. @@ -1013,7 +1012,6 @@ export interface ValidatorType(schema: S, formData?: T): { errors?: Result[]; validationError?: Error }; - /** An optional function that can be used to reset validator implementation. Useful for clear schemas in the AJV * instance for tests. */ @@ -1039,7 +1036,6 @@ export interface SchemaUtilsType; - /** 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. @@ -1056,7 +1052,6 @@ export interface SchemaUtilsType ): 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`. * @@ -1072,7 +1067,6 @@ export interface SchemaUtilsType, globalOptions?: GlobalUISchemaOptions): boolean; - /** Determines which of the given `options` provided most closely matches the `formData`. * Returns the index of the option that is valid and is the closest match, or 0 if there is no match. * @@ -1102,7 +1095,6 @@ export interface SchemaUtilsType): 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: 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: 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 @@ -1160,7 +1147,6 @@ export interface SchemaUtilsType, additionalErrorSchema?: ErrorSchema): ValidationData; - /** Retrieves an expanded schema that has had all of its conditions, additional properties, references and * dependencies resolved and merged into the `schema` given a `rawFormData` that is used to do the potentially * recursive resolution. @@ -1170,7 +1156,6 @@ export interface SchemaUtilsType; - /** Generates an `PathSchema` object for the `schema`, recursively * * @param schema - The schema for which the display label flag is desired From a46c87f1c39cb4bca47f10aab675f98481ba732e Mon Sep 17 00:00:00 2001 From: "LFRW2K\\lecmil2" Date: Mon, 4 Nov 2024 09:57:52 +0100 Subject: [PATCH 09/13] [FIX] Template type --- packages/utils/src/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/utils/src/types.ts b/packages/utils/src/types.ts index ab39f922ad..96067b6895 100644 --- a/packages/utils/src/types.ts +++ b/packages/utils/src/types.ts @@ -333,7 +333,7 @@ export type TemplatesType | { [key: string]: ComponentType }; + [key: string]: ComponentType | { [key: string]: ComponentType } | undefined; }; /** The set of UiSchema options that can be set globally and used as fallbacks at an individual template, field or From e447c9c5884ee01abfae253259cd972eca305e64 Mon Sep 17 00:00:00 2001 From: oddwerth Date: Sun, 23 Mar 2025 11:36:47 +0100 Subject: [PATCH 10/13] FIX: Fixed tests --- packages/utils/test/getTemplate.test.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/utils/test/getTemplate.test.ts b/packages/utils/test/getTemplate.test.ts index 231aa3bc0e..156a8038ec 100644 --- a/packages/utils/test/getTemplate.test.ts +++ b/packages/utils/test/getTemplate.test.ts @@ -8,8 +8,10 @@ import { UIOptionsType, } from '../src'; import getTestValidator from './testUtils/getTestValidator'; +import { ComponentType } from 'react'; +import cloneDeep from 'lodash/cloneDeep'; -const FakeTemplate = () => null; +const FakeTemplate: ComponentType = () => null; const CustomTemplate = () => undefined; @@ -103,9 +105,11 @@ describe('getTemplate', () => { }); it('returns the custom template name from the registry', () => { const customTemplateKey = 'CustomTemplate'; - registry.templates[customTemplateKey] = FakeTemplate; + const newRegistry = cloneDeep(registry); - expect(getTemplate(customTemplateKey, registry)).toBe(FakeTemplate); + newRegistry.templates[customTemplateKey] = FakeTemplate; + + expect(getTemplate(customTemplateKey, newRegistry)).toBe(FakeTemplate); }); it('returns undefined when the custom template is not in the registry', () => { From da78a2b77914521f9e9133e86505a2625266d4cc Mon Sep 17 00:00:00 2001 From: oddwerth Date: Sun, 23 Mar 2025 11:44:04 +0100 Subject: [PATCH 11/13] FIX getTemplate.test.ts --- packages/utils/test/getTemplate.test.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/utils/test/getTemplate.test.ts b/packages/utils/test/getTemplate.test.ts index 231aa3bc0e..8a3798d825 100644 --- a/packages/utils/test/getTemplate.test.ts +++ b/packages/utils/test/getTemplate.test.ts @@ -8,6 +8,7 @@ import { UIOptionsType, } from '../src'; import getTestValidator from './testUtils/getTestValidator'; +import cloneDeep from 'lodash/cloneDeep'; const FakeTemplate = () => null; @@ -103,9 +104,11 @@ describe('getTemplate', () => { }); it('returns the custom template name from the registry', () => { const customTemplateKey = 'CustomTemplate'; - registry.templates[customTemplateKey] = FakeTemplate; + const newRegistry = cloneDeep(registry); - expect(getTemplate(customTemplateKey, registry)).toBe(FakeTemplate); + newRegistry.templates[customTemplateKey] = FakeTemplate; + + expect(getTemplate(customTemplateKey, newRegistry)).toBe(FakeTemplate); }); it('returns undefined when the custom template is not in the registry', () => { From f6c5888768fe9b947fea4519a204ad6795f0c993 Mon Sep 17 00:00:00 2001 From: oddwerth Date: Sun, 23 Mar 2025 12:10:59 +0100 Subject: [PATCH 12/13] DOCS: Add section for custom templates in registry --- .../advanced-customization/custom-templates.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/packages/docs/docs/advanced-customization/custom-templates.md b/packages/docs/docs/advanced-customization/custom-templates.md index 7ed3c44239..a7854ac9fa 100644 --- a/packages/docs/docs/advanced-customization/custom-templates.md +++ b/packages/docs/docs/advanced-customization/custom-templates.md @@ -1105,3 +1105,20 @@ The following prop is passed to a `SubmitButton`: - `uiSchema`: The uiSchema object for this field, used to extract the `UISchemaSubmitButtonOptions`. - `registry`: The `registry` object. + +## Custom Templates + +You can now add custom components to the registry and reference them in your `uiSchema` using string keys. + +### Adding Custom Templates to the Registry + +```tsx +import CustomArrayFieldTemplate from './CustomArrayFieldTemplate'; +import { UiSchema } from '@rjsf/utils'; + +// Add the custom template to the registry +const registry = { templates: { CustomArrayFieldTemplate } }; + +// Use the custom template in the uiSchema +const uiSchema: UiSchema = { 'ui:ArrayFieldTemplate': 'CustomArrayFieldTemplate' }; +``` From a30d506a205bd37a196dd572c44a8a6dfd995fd8 Mon Sep 17 00:00:00 2001 From: oddwerth Date: Thu, 27 Mar 2025 11:14:35 +0100 Subject: [PATCH 13/13] FIX: Node 14 compat --- packages/utils/src/getTemplate.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/utils/src/getTemplate.ts b/packages/utils/src/getTemplate.ts index ca9733d53f..ab57d45f65 100644 --- a/packages/utils/src/getTemplate.ts +++ b/packages/utils/src/getTemplate.ts @@ -20,9 +20,9 @@ export default function getTemplate< } // Allow templates to be customized per-field by using string keys from the registry if ( - Object.hasOwn(uiOptions, name) && + Object.prototype.hasOwnProperty.call(uiOptions, name) && typeof uiOptions[name] === 'string' && - Object.hasOwn(templates, uiOptions[name] as string) + Object.prototype.hasOwnProperty.call(templates, uiOptions[name] as string) ) { const key = uiOptions[name]; // Evaluating templates[key] results in TS2590: Expression produces a union type that is too complex to represent