Skip to content

Commit 01969f9

Browse files
Added support for patternProperties
1 parent d45af8b commit 01969f9

File tree

15 files changed

+382
-58
lines changed

15 files changed

+382
-58
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,16 @@ should change the heading of the (upcoming) version to include a major version b
1616
1717
-->
1818

19+
# 6.0.0
20+
21+
## @rjsf/core
22+
23+
- Add support for `patternProperties` [#1944](https://github.com/rjsf-team/react-jsonschema-form/issues/1944)
24+
25+
## @rjsf/utils
26+
27+
- Add support for `patternProperties` [#1944](https://github.com/rjsf-team/react-jsonschema-form/issues/1944)
28+
1929
# 5.24.9
2030

2131
## @rjsf/antd

packages/core/src/components/fields/ObjectField.tsx

Lines changed: 27 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -195,36 +195,39 @@ class ObjectField<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends Fo
195195
* @param schema - The schema element to which the new property is being added
196196
*/
197197
handleAddClick = (schema: S) => () => {
198-
if (!schema.additionalProperties) {
198+
if (!(schema.patternProperties || schema.additionalProperties)) {
199199
return;
200200
}
201201
const { formData, onChange, registry } = this.props;
202202
const newFormData = { ...formData } as T;
203-
204-
let type: RJSFSchema['type'] = undefined;
205-
let constValue: RJSFSchema['const'] = undefined;
206-
let defaultValue: RJSFSchema['default'] = undefined;
207-
if (isObject(schema.additionalProperties)) {
208-
type = schema.additionalProperties.type;
209-
constValue = schema.additionalProperties.const;
210-
defaultValue = schema.additionalProperties.default;
211-
let apSchema = schema.additionalProperties;
212-
if (REF_KEY in apSchema) {
213-
const { schemaUtils } = registry;
214-
apSchema = schemaUtils.retrieveSchema({ $ref: apSchema[REF_KEY] } as S, formData);
215-
type = apSchema.type;
216-
constValue = apSchema.const;
217-
defaultValue = apSchema.default;
218-
}
219-
if (!type && (ANY_OF_KEY in apSchema || ONE_OF_KEY in apSchema)) {
220-
type = 'object';
203+
const newKey = this.getAvailableKey('newKey', newFormData);
204+
if (schema.patternProperties) {
205+
set(newFormData as GenericObjectType, newKey, null);
206+
} else {
207+
let type: RJSFSchema['type'] = undefined;
208+
let constValue: RJSFSchema['const'] = undefined;
209+
let defaultValue: RJSFSchema['default'] = undefined;
210+
if (isObject(schema.additionalProperties)) {
211+
type = schema.additionalProperties.type;
212+
constValue = schema.additionalProperties.const;
213+
defaultValue = schema.additionalProperties.default;
214+
let apSchema = schema.additionalProperties;
215+
if (REF_KEY in apSchema) {
216+
const { schemaUtils } = registry;
217+
apSchema = schemaUtils.retrieveSchema({ $ref: apSchema[REF_KEY] } as S, formData);
218+
type = apSchema.type;
219+
constValue = apSchema.const;
220+
defaultValue = apSchema.default;
221+
}
222+
if (!type && (ANY_OF_KEY in apSchema || ONE_OF_KEY in apSchema)) {
223+
type = 'object';
224+
}
221225
}
222-
}
223226

224-
const newKey = this.getAvailableKey('newKey', newFormData);
225-
const newValue = constValue ?? defaultValue ?? this.getDefaultValue(type);
226-
// Cast this to make the `set` work properly
227-
set(newFormData as GenericObjectType, newKey, newValue);
227+
const newValue = constValue ?? defaultValue ?? this.getDefaultValue(type);
228+
// Cast this to make the `set` work properly
229+
set(newFormData as GenericObjectType, newKey, newValue);
230+
}
228231

229232
onChange(newFormData);
230233
};

packages/docs/docs/advanced-customization/custom-templates.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ In version 5, all existing `templates` were consolidated into a new `TemplatesTy
1414
They can also be overloaded globally on the `Form` via the `templates` prop as well as globally or per-field through the `uiSchema`.
1515
Further, many new templates were added or repurposed from existing `widgets` and `fields` in an effort to simplify the effort needed by theme authors to build new and/or maintain current themes.
1616
These new templates can also be overridden by individual users to customize the specific needs of their application.
17-
A special category of templates, `ButtonTemplates`, were also added to support the easy replacement of the `Submit` button on the form, the `Add` and `Remove` buttons associated with `additionalProperties` on objects and elements of arrays, as well as the `Move up` and `Move down` buttons used for reordering arrays.
17+
A special category of templates, `ButtonTemplates`, were also added to support the easy replacement of the `Submit` button on the form, the `Add` and `Remove` buttons associated with `additionalProperties` and `patternProperties` on objects and elements of arrays, as well as the `Move up` and `Move down` buttons used for reordering arrays.
1818
This category, unlike the others, can only be overridden globally via the `templates` prop on `Form`.
1919

2020
Below is the table that lists all the `templates`, their props interface, their `uiSchema` name and from where they originated in the previous version of RJSF:
@@ -461,7 +461,7 @@ The following props are passed to the `BaseInputTemplate`:
461461
- `multiple`: A boolean value stating if the widget can accept multiple values;
462462
- `onChange`: The value change event handler; call it with the new value every time it changes;
463463
- `onChangeOverride`: 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;
464-
- `onKeyChange`: The key change event handler (only called for fields with `additionalProperties`); pass the new value every time it changes;
464+
- `onKeyChange`: The key change event handler (only called for fields with `additionalProperties` and `patternProperties`); pass the new value every time it changes;
465465
- `onBlur`: The input blur event handler; call it with the widget id and value;
466466
- `onFocus`: The input focus event handler; call it with the widget id and value;
467467
- `options`: A map of options passed as a prop to the component (see [Custom widget options](./custom-widgets-fields.md#custom-widget-options)).
@@ -807,7 +807,7 @@ The following props are passed to each `ObjectFieldTemplate` as defined by the `
807807
- `description`: A string value containing the description for the object.
808808
- `disabled`: A boolean value stating if the object is disabled.
809809
- `properties`: An array of object representing the properties in the object. Each of the properties represent a child with properties described below.
810-
- `onAddClick: (schema: RJSFSchema) => () => void`: Returns a function that adds a new property to the object (to be used with additionalProperties)
810+
- `onAddClick: (schema: RJSFSchema) => () => void`: Returns a function that adds a new property to the object (to be used with additionalProperties and patternProperties)
811811
- `readonly`: A boolean value stating if the object is read-only.
812812
- `required`: A boolean value stating if the object is required.
813813
- `hideError`: A boolean value stating if the field is hiding its errors.
@@ -908,8 +908,8 @@ The following props are passed to each `UnsupportedFieldTemplate`:
908908

909909
## WrapIfAdditionalTemplate
910910

911-
The `WrapIfAdditionalTemplate` is used by the `FieldTemplate` to conditionally render additional controls if `additionalProperties` is present in the schema.
912-
You may customize `WrapIfAdditionalTemplate` if you wish to change the layout or behavior of user-controlled `additionalProperties`.
911+
The `WrapIfAdditionalTemplate` is used by the `FieldTemplate` to conditionally render additional controls if `additionalProperties` or `patternProperties` are present in the schema.
912+
You may customize `WrapIfAdditionalTemplate` if you wish to change the layout or behavior of user-controlled `additionalProperties` and `patternProperties`.
913913

914914
```tsx
915915
import { RJSFSchema, WrapIfAdditionalTemplateProps } from '@rjsf/utils';
@@ -987,7 +987,7 @@ Each button template (except for the `SubmitButton`) accepts, as props, the stan
987987

988988
### AddButton
989989

990-
The `AddButton` is used to render an add action on a `Form` for both a new `additionalProperties` element for an object or a new element in an array.
990+
The `AddButton` is used to render an add action on a `Form` for both a new `additionalProperties` or `patternProperties` element for an object or a new element in an array.
991991
You can customize the `AddButton` to render something other than the icon button that is provided by a theme as follows:
992992

993993
```tsx
@@ -1077,7 +1077,7 @@ render(
10771077

10781078
### RemoveButton
10791079

1080-
The `RemoveButton` is used to render a remove action on a `Form` for both a existing `additionalProperties` element for an object or an existing element in an array.
1080+
The `RemoveButton` is used to render a remove action on a `Form` for both a existing `additionalProperties` or `patternProperties` element for an object or an existing element in an array.
10811081
You can customize the `RemoveButton` to render something other than the icon button that is provided by a theme as follows:
10821082

10831083
```tsx

packages/docs/docs/api-reference/form-props.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -464,7 +464,7 @@ Sometimes you may want to trigger events or modify external state when a field h
464464
If you plan on being notified every time the form data are updated, you can pass an `onChange` handler, which will receive the same first argument as `onSubmit` any time a value is updated in the form.
465465
It will also receive, as the second argument, the `id` of the field which experienced the change.
466466
Generally, this will be the `id` of the field for which input data is modified.
467-
In the case of adding/removing of new fields in arrays or objects with `additionalProperties` and the rearranging of items in arrays, the `id` will be that of the array or object itself, rather than the item/field being added, removed or moved.
467+
In the case of adding/removing of new fields in arrays or objects with `additionalProperties` or `patternProperties` and the rearranging of items in arrays, the `id` will be that of the array or object itself, rather than the item/field being added, removed or moved.
468468

469469
## onError
470470

packages/docs/docs/api-reference/utility-functions.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ The UI for the field can expand if it has additional properties, is not forced a
9696

9797
#### Returns
9898

99-
- boolean: True if the schema element has additionalProperties, is expandable, and not at the maxProperties limit
99+
- boolean: True if the schema element has additionalProperties or patternProperties keywords, is expandable, and not at the maxProperties limit
100100

101101
### createErrorHandler<T = any>()
102102

@@ -392,6 +392,7 @@ If the type is not explicitly defined, then an attempt is made to infer it from
392392
- schema.enum: Returns `string`
393393
- schema.properties: Returns `object`
394394
- schema.additionalProperties: Returns `object`
395+
- schema.patternProperties: Returns `object`
395396
- type is an array with a length of 2 and one type is 'null': Returns the other type
396397

397398
#### Parameters

packages/docs/docs/json-schema/objects.md

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ const uiSchema: UiSchema = {
8787
};
8888
```
8989

90-
## Additional properties
90+
## Additional and pattern properties
9191

9292
The `additionalProperties` keyword allows the user to add properties with arbitrary key names. Set this keyword equal to a schema object:
9393

@@ -116,9 +116,36 @@ In this way, an add button for new properties is shown by default.
116116

117117
You can also define `uiSchema` options for `additionalProperties` by setting the `additionalProperties` attribute in the `uiSchema`.
118118

119+
The `patternProperties` keyword allows the user to add properties with names that match one or more of the specified regular expressions
120+
121+
```tsx
122+
import { Form } from '@rjsf/core';
123+
import { RJSFSchema } from '@rjsf/utils';
124+
import validator from '@rjsf/validator-ajv8';
125+
126+
const schema: RJSFSchema = {
127+
type: 'object',
128+
properties: {
129+
name: {
130+
type: 'string',
131+
},
132+
},
133+
patternProperties: {
134+
'^foo+$': {
135+
type: 'number',
136+
enum: [1, 2, 3],
137+
},
138+
},
139+
};
140+
141+
render(<Form schema={schema} validator={validator} />, document.getElementById('app'));
142+
```
143+
144+
Also in this case, an add button for new properties is shown by default.
145+
119146
### `expandable` option
120147

121-
You can turn support for `additionalProperties` off with the `expandable` option in `uiSchema`:
148+
You can turn support for `additionalProperties` and `patternProperties` off with the `expandable` option in `uiSchema`:
122149

123150
```ts
124151
import { UiSchema } from '@rjsf/utils';

packages/playground/src/samples/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import customField from './customField';
3434
import layoutGrid from './layoutGrid';
3535
import { Sample } from './Sample';
3636
import deepFreeze from 'deep-freeze-es6';
37+
import patternProperties from './patternProperties';
3738

3839
export type { Sample };
3940

@@ -61,6 +62,7 @@ const _samples: Record<string, Sample> = {
6162
'Property dependencies': propertyDependencies,
6263
'Schema dependencies': schemaDependencies,
6364
'Additional Properties': additionalProperties,
65+
'Pattern Properties': patternProperties,
6466
'Any Of': anyOf,
6567
'Any Of with Custom Field': customFieldAnyOf,
6668
'One Of': oneOf,
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { Sample } from './Sample';
2+
3+
const patternProperties: Sample = {
4+
schema: {
5+
title: 'A customizable registration form',
6+
description: 'A simple form with pattern properties example.',
7+
type: 'object',
8+
required: ['firstName', 'lastName'],
9+
properties: {
10+
firstName: {
11+
type: 'string',
12+
title: 'First name',
13+
},
14+
lastName: {
15+
type: 'string',
16+
title: 'Last name',
17+
},
18+
},
19+
patternProperties: {
20+
'^[a-z][a-zA-Z]+$': {
21+
type: 'string',
22+
},
23+
},
24+
},
25+
uiSchema: {
26+
firstName: {
27+
'ui:autofocus': true,
28+
'ui:emptyValue': '',
29+
},
30+
},
31+
formData: {
32+
firstName: 'Chuck',
33+
lastName: 'Norris',
34+
assKickCount: 'infinity',
35+
},
36+
};
37+
38+
export default patternProperties;

packages/utils/src/canExpand.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export default function canExpand<T = any, S extends StrictRJSFSchema = RJSFSche
1515
uiSchema: UiSchema<T, S, F> = {},
1616
formData?: T,
1717
) {
18-
if (!schema.additionalProperties) {
18+
if (!(schema.additionalProperties || schema.patternProperties)) {
1919
return false;
2020
}
2121
const { expandable = true } = getUiOptions<T, S, F>(uiSchema);

packages/utils/src/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export const ITEMS_KEY = 'items';
1919
export const JUNK_OPTION_ID = '_$junk_option_schema_id$_';
2020
export const NAME_KEY = '$name';
2121
export const ONE_OF_KEY = 'oneOf';
22+
export const PATTERN_PROPERTIES_KEY = 'patternProperties';
2223
export const PROPERTIES_KEY = 'properties';
2324
export const READONLY_KEY = 'readonly';
2425
export const REQUIRED_KEY = 'required';

0 commit comments

Comments
 (0)