@rjsf/utils 6.0.0-beta.12 → 6.0.0-beta.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/index.js +100 -23
  2. package/dist/index.js.map +4 -4
  3. package/dist/utils.esm.js +100 -23
  4. package/dist/utils.esm.js.map +4 -4
  5. package/dist/utils.umd.js +98 -24
  6. package/lib/constants.d.ts +1 -0
  7. package/lib/constants.js +1 -0
  8. package/lib/constants.js.map +1 -1
  9. package/lib/createSchemaUtils.js +11 -1
  10. package/lib/createSchemaUtils.js.map +1 -1
  11. package/lib/findSchemaDefinition.d.ts +6 -0
  12. package/lib/findSchemaDefinition.js +44 -3
  13. package/lib/findSchemaDefinition.js.map +1 -1
  14. package/lib/getUiOptions.js +4 -0
  15. package/lib/getUiOptions.js.map +1 -1
  16. package/lib/index.d.ts +3 -1
  17. package/lib/index.js +2 -1
  18. package/lib/index.js.map +1 -1
  19. package/lib/mergeDefaultsWithFormData.js +2 -2
  20. package/lib/mergeDefaultsWithFormData.js.map +1 -1
  21. package/lib/schema/getDefaultFormState.js +10 -2
  22. package/lib/schema/getDefaultFormState.js.map +1 -1
  23. package/lib/schema/getDisplayLabel.js +2 -2
  24. package/lib/schema/getDisplayLabel.js.map +1 -1
  25. package/lib/schema/retrieveSchema.js +2 -2
  26. package/lib/schema/retrieveSchema.js.map +1 -1
  27. package/lib/shallowEquals.d.ts +8 -0
  28. package/lib/shallowEquals.js +36 -0
  29. package/lib/shallowEquals.js.map +1 -0
  30. package/lib/shouldRender.d.ts +8 -2
  31. package/lib/shouldRender.js +17 -2
  32. package/lib/shouldRender.js.map +1 -1
  33. package/lib/tsconfig.tsbuildinfo +1 -1
  34. package/lib/types.d.ts +20 -3
  35. package/package.json +5 -5
  36. package/src/constants.ts +1 -0
  37. package/src/createSchemaUtils.ts +11 -1
  38. package/src/findSchemaDefinition.ts +51 -3
  39. package/src/getUiOptions.ts +4 -0
  40. package/src/index.ts +4 -0
  41. package/src/mergeDefaultsWithFormData.ts +1 -1
  42. package/src/schema/getDefaultFormState.ts +12 -2
  43. package/src/schema/getDisplayLabel.ts +2 -2
  44. package/src/schema/retrieveSchema.ts +2 -2
  45. package/src/shallowEquals.ts +41 -0
  46. package/src/shouldRender.ts +27 -2
  47. package/src/types.ts +23 -3
package/lib/types.d.ts CHANGED
@@ -174,6 +174,8 @@ export type RJSFValidationError = {
174
174
  schemaPath?: string;
175
175
  /** Full error name, for example ".name is a required property" */
176
176
  stack: string;
177
+ /** The title property for the failing field*/
178
+ title?: string;
177
179
  };
178
180
  /** The type that describes an error in a field */
179
181
  export type FieldError = string;
@@ -358,6 +360,8 @@ export interface Registry<T = any, S extends StrictRJSFSchema = RJSFSchema, F ex
358
360
  translateString: (stringKey: TranslatableString, params?: string[]) => string;
359
361
  /** The optional global UI Options that are available for all templates, fields and widgets to access */
360
362
  globalUiOptions?: GlobalUISchemaOptions;
363
+ /** The component update strategy used by the Form and its fields for performance optimization */
364
+ experimental_componentUpdateStrategy?: 'customDeep' | 'shallow' | 'always';
361
365
  }
362
366
  /** The properties that are passed to a Field implementation */
363
367
  export interface FieldProps<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any> extends GenericObjectType, RJSFBaseProps<T, S, F>, Pick<HTMLAttributes<HTMLElement>, Exclude<keyof HTMLAttributes<HTMLElement>, 'onBlur' | 'onFocus' | 'onChange'>> {
@@ -367,8 +371,10 @@ export interface FieldProps<T = any, S extends StrictRJSFSchema = RJSFSchema, F
367
371
  formData?: T;
368
372
  /** The tree of errors for this field and its children */
369
373
  errorSchema?: ErrorSchema<T>;
370
- /** The field change event handler; called with the updated form data and an optional `ErrorSchema` */
371
- onChange: (newFormData: T | undefined, es?: ErrorSchema<T>, id?: string) => any;
374
+ /** The field change event handler; called with the updated field value, the optional change path for the value
375
+ * (defaults to an empty array), an optional ErrorSchema and the optional id of the field being changed
376
+ */
377
+ onChange: (newValue: T | undefined, path?: (number | string)[], es?: ErrorSchema<T>, id?: string) => void;
372
378
  /** The input blur event handler; call it with the field id and value */
373
379
  onBlur: (id: string, value: any) => void;
374
380
  /** The input focus event handler; call it with the field id and value */
@@ -637,7 +643,7 @@ export interface MultiSchemaFieldTemplateProps<T = any, S extends StrictRJSFSche
637
643
  optionSchemaField: ReactNode;
638
644
  }
639
645
  /** The properties that are passed to a Widget implementation */
640
- export interface WidgetProps<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any> extends GenericObjectType, RJSFBaseProps<T, S, F>, Pick<HTMLAttributes<HTMLElement>, Exclude<keyof HTMLAttributes<HTMLElement>, 'onBlur' | 'onFocus'>> {
646
+ export interface WidgetProps<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any> extends GenericObjectType, RJSFBaseProps<T, S, F>, Pick<HTMLAttributes<HTMLElement>, Exclude<keyof HTMLAttributes<HTMLElement>, 'onBlur' | 'onFocus' | 'onChange'>> {
641
647
  /** The generated id for this widget, used to provide unique `name`s and `id`s for the HTML field elements rendered by
642
648
  * widgets
643
649
  */
@@ -801,6 +807,12 @@ export type UIOptionsType<T = any, S extends StrictRJSFSchema = RJSFSchema, F ex
801
807
  /** Anything else will be one of these types */
802
808
  [key: string]: boolean | number | string | object | any[] | null | undefined;
803
809
  };
810
+ /**
811
+ * A utility type that extracts the element type from an array type.
812
+ * If the type is not an array, it returns the type itself as a safe fallback.
813
+ * Handles both standard arrays and readonly arrays.
814
+ */
815
+ export type ArrayElement<A> = A extends readonly (infer E)[] ? E : A;
804
816
  /** Type describing the well-known properties of the `UiSchema` while also supporting all user defined properties,
805
817
  * starting with `ui:`.
806
818
  */
@@ -821,6 +833,11 @@ export type UiSchema<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends
821
833
  'ui:fieldReplacesAnyOrOneOf'?: boolean;
822
834
  /** An object that contains all the potential UI options in a single object */
823
835
  'ui:options'?: UIOptionsType<T, S, F>;
836
+ /** The uiSchema for items in an array. Can be an object for a uniform uiSchema across all items (current behavior),
837
+ * or a function that returns a dynamic uiSchema based on the item's data and index.
838
+ * When using a function, it receives the item data, index, and optionally the form context as parameters.
839
+ */
840
+ items?: UiSchema<ArrayElement<T>, S, F> | ((itemData: ArrayElement<T>, index: number, formContext?: F) => UiSchema<ArrayElement<T>, S, F>);
824
841
  };
825
842
  /** A `CustomValidator` function takes in a `formData`, `errors` and `uiSchema` objects and returns the given `errors`
826
843
  * object back, while potentially adding additional messages to the `errors`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rjsf/utils",
3
- "version": "6.0.0-beta.12",
3
+ "version": "6.0.0-beta.14",
4
4
  "main": "dist/index.js",
5
5
  "module": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
@@ -71,14 +71,14 @@
71
71
  "lodash": "^4.17.21",
72
72
  "lodash-es": "^4.17.21",
73
73
  "nanoid": "^5.1.5",
74
- "react-is": "^18.2.0"
74
+ "react-is": "^18.3.1"
75
75
  },
76
76
  "devDependencies": {
77
77
  "@types/json-schema": "^7.0.15",
78
78
  "@types/json-schema-merge-allof": "^0.6.5",
79
- "@types/react-is": "^18.2.4",
80
- "deep-freeze-es6": "^1.4.1",
81
- "eslint": "^8.56.0"
79
+ "@types/react-is": "^18.3.1",
80
+ "deep-freeze-es6": "^4.0.1",
81
+ "eslint": "^8.57.1"
82
82
  },
83
83
  "publishConfig": {
84
84
  "access": "public"
package/src/constants.ts CHANGED
@@ -46,4 +46,5 @@ export const UI_GLOBAL_OPTIONS_KEY = 'ui:globalOptions';
46
46
 
47
47
  /** The JSON Schema version strings
48
48
  */
49
+ export const JSON_SCHEMA_DRAFT_2019_09 = 'https://json-schema.org/draft/2019-09/schema';
49
50
  export const JSON_SCHEMA_DRAFT_2020_12 = 'https://json-schema.org/draft/2020-12/schema';
@@ -29,6 +29,9 @@ import {
29
29
  toIdSchema,
30
30
  toPathSchema,
31
31
  } from './schema';
32
+ import { makeAllReferencesAbsolute } from './findSchemaDefinition';
33
+ import { ID_KEY, JSON_SCHEMA_DRAFT_2020_12, SCHEMA_KEY } from './constants';
34
+ import get from 'lodash/get';
32
35
 
33
36
  /** The `SchemaUtils` class provides a wrapper around the publicly exported APIs in the `utils/schema` directory such
34
37
  * that one does not have to explicitly pass the `validator`, `rootSchema`, `experimental_defaultFormStateBehavior` or
@@ -57,7 +60,11 @@ class SchemaUtils<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends Fo
57
60
  experimental_defaultFormStateBehavior: Experimental_DefaultFormStateBehavior,
58
61
  experimental_customMergeAllOf?: Experimental_CustomMergeAllOf<S>,
59
62
  ) {
60
- this.rootSchema = rootSchema;
63
+ if (rootSchema && rootSchema[SCHEMA_KEY] === JSON_SCHEMA_DRAFT_2020_12) {
64
+ this.rootSchema = makeAllReferencesAbsolute(rootSchema, get(rootSchema, ID_KEY, '#'));
65
+ } else {
66
+ this.rootSchema = rootSchema;
67
+ }
61
68
  this.validator = validator;
62
69
  this.experimental_defaultFormStateBehavior = experimental_defaultFormStateBehavior;
63
70
  this.experimental_customMergeAllOf = experimental_customMergeAllOf;
@@ -95,9 +102,12 @@ class SchemaUtils<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends Fo
95
102
  experimental_defaultFormStateBehavior = {},
96
103
  experimental_customMergeAllOf?: Experimental_CustomMergeAllOf<S>,
97
104
  ): boolean {
105
+ // If either validator or rootSchema are falsy, return false to prevent the creation
106
+ // of a new SchemaUtilsType with incomplete properties.
98
107
  if (!validator || !rootSchema) {
99
108
  return false;
100
109
  }
110
+
101
111
  return (
102
112
  this.validator !== validator ||
103
113
  !deepEquals(this.rootSchema, rootSchema) ||
@@ -1,7 +1,14 @@
1
1
  import jsonpointer from 'jsonpointer';
2
2
  import omit from 'lodash/omit';
3
3
 
4
- import { ID_KEY, JSON_SCHEMA_DRAFT_2020_12, REF_KEY, SCHEMA_KEY } from './constants';
4
+ import {
5
+ ALL_OF_KEY,
6
+ ID_KEY,
7
+ JSON_SCHEMA_DRAFT_2019_09,
8
+ JSON_SCHEMA_DRAFT_2020_12,
9
+ REF_KEY,
10
+ SCHEMA_KEY,
11
+ } from './constants';
5
12
  import { GenericObjectType, RJSFSchema, StrictRJSFSchema } from './types';
6
13
  import isObject from 'lodash/isObject';
7
14
  import isEmpty from 'lodash/isEmpty';
@@ -20,7 +27,16 @@ function findEmbeddedSchemaRecursive<S extends StrictRJSFSchema = RJSFSchema>(sc
20
27
  return schema;
21
28
  }
22
29
  for (const subSchema of Object.values(schema)) {
23
- if (isObject(subSchema)) {
30
+ if (Array.isArray(subSchema)) {
31
+ for (const item of subSchema) {
32
+ if (isObject(item)) {
33
+ const result = findEmbeddedSchemaRecursive<S>(item as S, ref);
34
+ if (result !== undefined) {
35
+ return result as S;
36
+ }
37
+ }
38
+ }
39
+ } else if (isObject(subSchema)) {
24
40
  const result = findEmbeddedSchemaRecursive<S>(subSchema as S, ref);
25
41
  if (result !== undefined) {
26
42
  return result as S;
@@ -30,6 +46,31 @@ function findEmbeddedSchemaRecursive<S extends StrictRJSFSchema = RJSFSchema>(sc
30
46
  return undefined;
31
47
  }
32
48
 
49
+ /** Parses a JSONSchema and makes all references absolute with respect to
50
+ * the `baseURI` argument
51
+ * @param schema - The schema to be processed
52
+ * @param baseURI - The base URI to be used for resolving relative references
53
+ */
54
+ export function makeAllReferencesAbsolute<S extends StrictRJSFSchema = RJSFSchema>(schema: S, baseURI: string): S {
55
+ const currentURI = get(schema, ID_KEY, baseURI);
56
+ // Make all other references absolute
57
+ if (REF_KEY in schema) {
58
+ schema = { ...schema, [REF_KEY]: UriResolver.resolve(currentURI, schema[REF_KEY]!) };
59
+ }
60
+ // Look for references in nested subschemas
61
+ for (const [key, subSchema] of Object.entries(schema)) {
62
+ if (Array.isArray(subSchema)) {
63
+ schema = {
64
+ ...schema,
65
+ [key]: subSchema.map((item) => (isObject(item) ? makeAllReferencesAbsolute(item as S, currentURI) : item)),
66
+ };
67
+ } else if (isObject(subSchema)) {
68
+ schema = { ...schema, [key]: makeAllReferencesAbsolute(subSchema as S, currentURI) };
69
+ }
70
+ }
71
+ return schema;
72
+ }
73
+
33
74
  /** Splits out the value at the `key` in `object` from the `object`, returning an array that contains in the first
34
75
  * location, the `object` minus the `key: value` and in the second location the `value`.
35
76
  *
@@ -103,7 +144,14 @@ export function findSchemaDefinitionRecursive<S extends StrictRJSFSchema = RJSFS
103
144
  const [remaining, theRef] = splitKeyElementFromObject(REF_KEY, current);
104
145
  const subSchema = findSchemaDefinitionRecursive<S>(theRef, rootSchema, [...recurseList, ref], baseURI);
105
146
  if (Object.keys(remaining).length > 0) {
106
- return { ...remaining, ...subSchema };
147
+ if (
148
+ rootSchema[SCHEMA_KEY] === JSON_SCHEMA_DRAFT_2019_09 ||
149
+ rootSchema[SCHEMA_KEY] === JSON_SCHEMA_DRAFT_2020_12
150
+ ) {
151
+ return { [ALL_OF_KEY]: [remaining, subSchema] } as S;
152
+ } else {
153
+ return { ...remaining, ...subSchema };
154
+ }
107
155
  }
108
156
  return subSchema;
109
157
  }
@@ -13,6 +13,10 @@ export default function getUiOptions<T = any, S extends StrictRJSFSchema = RJSFS
13
13
  uiSchema: UiSchema<T, S, F> = {},
14
14
  globalOptions: GlobalUISchemaOptions = {},
15
15
  ): UIOptionsType<T, S, F> {
16
+ // Handle null or undefined uiSchema
17
+ if (!uiSchema) {
18
+ return { ...globalOptions };
19
+ }
16
20
  return Object.keys(uiSchema)
17
21
  .filter((key) => key.indexOf('ui:') === 0)
18
22
  .reduce(
package/src/index.ts CHANGED
@@ -6,6 +6,7 @@ import createSchemaUtils from './createSchemaUtils';
6
6
  import dataURItoBlob from './dataURItoBlob';
7
7
  import dateRangeOptions from './dateRangeOptions';
8
8
  import deepEquals from './deepEquals';
9
+ import shallowEquals from './shallowEquals';
9
10
  import englishStringTranslator from './englishStringTranslator';
10
11
  import enumOptionsDeselectValue from './enumOptionsDeselectValue';
11
12
  import enumOptionsIndexForValue from './enumOptionsIndexForValue';
@@ -130,6 +131,7 @@ export {
130
131
  rangeSpec,
131
132
  replaceStringParameters,
132
133
  schemaRequiresTrueValue,
134
+ shallowEquals,
133
135
  shouldRender,
134
136
  sortedJSONStringify,
135
137
  titleId,
@@ -142,3 +144,5 @@ export {
142
144
  validationDataMerge,
143
145
  withIdRefPrefix,
144
146
  };
147
+
148
+ export type { ComponentUpdateStrategy } from './shouldRender';
@@ -82,7 +82,7 @@ export default function mergeDefaultsWithFormData<T = any>(
82
82
  }
83
83
 
84
84
  acc[key as keyof T] = mergeDefaultsWithFormData<T>(
85
- get(defaults, key) ?? {},
85
+ get(defaults, key),
86
86
  keyValue,
87
87
  mergeExtraArrayDefaults,
88
88
  defaultSupercedesUndefined,
@@ -115,10 +115,18 @@ function maybeAddDefaultToObject<T = any>(
115
115
  isConst = false,
116
116
  ) {
117
117
  const { emptyObjectFields = 'populateAllDefaults' } = experimental_defaultFormStateBehavior;
118
- if (includeUndefinedValues || isConst) {
119
- // If includeUndefinedValues
118
+
119
+ if (includeUndefinedValues === true || isConst) {
120
+ // If includeUndefinedValues is explicitly true
120
121
  // Or if the schema has a const property defined, then we should always return the computedDefault since it's coming from the const.
121
122
  obj[key] = computedDefault;
123
+ } else if (includeUndefinedValues === 'excludeObjectChildren') {
124
+ // Fix for Issue #4709: When in 'excludeObjectChildren' mode, don't set primitive fields to empty objects
125
+ // Only add the computed default if it's not an empty object placeholder for a primitive field
126
+ if (!isObject(computedDefault) || !isEmpty(computedDefault)) {
127
+ obj[key] = computedDefault;
128
+ }
129
+ // If computedDefault is an empty object {}, don't add it - let the field stay undefined
122
130
  } else if (emptyObjectFields !== 'skipDefaults') {
123
131
  // If isParentRequired is undefined, then we are at the root level of the schema so defer to the requiredness of
124
132
  // the field key itself in the `requiredField` list
@@ -473,6 +481,7 @@ export function getObjectDefaults<T = any, S extends StrictRJSFSchema = RJSFSche
473
481
  required: retrievedSchema.required?.includes(key),
474
482
  shouldMergeDefaultsIntoFormData,
475
483
  });
484
+
476
485
  maybeAddDefaultToObject<T>(
477
486
  acc,
478
487
  key,
@@ -483,6 +492,7 @@ export function getObjectDefaults<T = any, S extends StrictRJSFSchema = RJSFSche
483
492
  experimental_defaultFormStateBehavior,
484
493
  hasConst,
485
494
  );
495
+
486
496
  return acc;
487
497
  },
488
498
  {},
@@ -52,10 +52,10 @@ export default function getDisplayLabel<
52
52
  if (schemaType === 'object') {
53
53
  displayLabel = false;
54
54
  }
55
- if (schemaType === 'boolean' && !uiSchema[UI_WIDGET_KEY]) {
55
+ if (schemaType === 'boolean' && uiSchema && !uiSchema[UI_WIDGET_KEY]) {
56
56
  displayLabel = false;
57
57
  }
58
- if (uiSchema[UI_FIELD_KEY]) {
58
+ if (uiSchema && uiSchema[UI_FIELD_KEY]) {
59
59
  displayLabel = false;
60
60
  }
61
61
  return displayLabel;
@@ -430,7 +430,7 @@ export function stubExistingAdditionalProperties<
430
430
  if (!isEmpty(matchingProperties)) {
431
431
  schema.properties[key] = retrieveSchema<T, S, F>(
432
432
  validator,
433
- { allOf: Object.values(matchingProperties) } as S,
433
+ { [ALL_OF_KEY]: Object.values(matchingProperties) } as S,
434
434
  rootSchema,
435
435
  get(formData, [key]) as T,
436
436
  experimental_customMergeAllOf,
@@ -445,7 +445,7 @@ export function stubExistingAdditionalProperties<
445
445
  if (REF_KEY in schema.additionalProperties!) {
446
446
  additionalProperties = retrieveSchema<T, S, F>(
447
447
  validator,
448
- { $ref: get(schema.additionalProperties, [REF_KEY]) } as S,
448
+ { [REF_KEY]: get(schema.additionalProperties, [REF_KEY]) } as S,
449
449
  rootSchema,
450
450
  formData as T,
451
451
  experimental_customMergeAllOf,
@@ -0,0 +1,41 @@
1
+ /** Implements a shallow equals comparison that uses Object.is() for comparing values.
2
+ * This function compares objects by checking if all keys and their values are equal using Object.is().
3
+ *
4
+ * @param a - The first element to compare
5
+ * @param b - The second element to compare
6
+ * @returns - True if the `a` and `b` are shallow equal, false otherwise
7
+ */
8
+ export default function shallowEquals(a: any, b: any): boolean {
9
+ // If they're the same reference, they're equal
10
+ if (Object.is(a, b)) {
11
+ return true;
12
+ }
13
+
14
+ // If either is null or undefined, they're not equal (since we know they're not the same reference)
15
+ if (a == null || b == null) {
16
+ return false;
17
+ }
18
+
19
+ // If they're not objects, they're not equal (since Object.is already checked)
20
+ if (typeof a !== 'object' || typeof b !== 'object') {
21
+ return false;
22
+ }
23
+
24
+ const keysA = Object.keys(a);
25
+ const keysB = Object.keys(b);
26
+
27
+ // Different number of keys means not equal
28
+ if (keysA.length !== keysB.length) {
29
+ return false;
30
+ }
31
+
32
+ // Check if all keys and values are equal
33
+ for (let i = 0; i < keysA.length; i++) {
34
+ const key = keysA[i];
35
+ if (!Object.prototype.hasOwnProperty.call(b, key) || !Object.is(a[key], b[key])) {
36
+ return false;
37
+ }
38
+ }
39
+
40
+ return true;
41
+ }
@@ -1,16 +1,41 @@
1
1
  import React from 'react';
2
2
 
3
3
  import deepEquals from './deepEquals';
4
+ import shallowEquals from './shallowEquals';
5
+
6
+ /** The supported component update strategies */
7
+ export type ComponentUpdateStrategy = 'customDeep' | 'shallow' | 'always';
4
8
 
5
9
  /** Determines whether the given `component` should be rerendered by comparing its current set of props and state
6
- * against the next set. If either of those two sets are not the same, then the component should be rerendered.
10
+ * against the next set. The comparison strategy can be controlled via the `updateStrategy` parameter.
7
11
  *
8
12
  * @param component - A React component being checked
9
13
  * @param nextProps - The next set of props against which to check
10
14
  * @param nextState - The next set of state against which to check
15
+ * @param updateStrategy - The strategy to use for comparison:
16
+ * - 'customDeep': Uses RJSF's custom deep equality checks (default)
17
+ * - 'shallow': Uses shallow comparison of props and state
18
+ * - 'always': Always returns true (React's default behavior)
11
19
  * @returns - True if the component should be re-rendered, false otherwise
12
20
  */
13
- export default function shouldRender(component: React.Component, nextProps: any, nextState: any) {
21
+ export default function shouldRender(
22
+ component: React.Component,
23
+ nextProps: any,
24
+ nextState: any,
25
+ updateStrategy: ComponentUpdateStrategy = 'customDeep',
26
+ ) {
27
+ if (updateStrategy === 'always') {
28
+ // Use React's default behavior: always update if state or props change (no shouldComponentUpdate optimization)
29
+ return true;
30
+ }
31
+
32
+ if (updateStrategy === 'shallow') {
33
+ // Use shallow comparison for props and state
34
+ const { props, state } = component;
35
+ return !shallowEquals(props, nextProps) || !shallowEquals(state, nextState);
36
+ }
37
+
38
+ // Use custom deep equality checks (default 'customDeep' strategy)
14
39
  const { props, state } = component;
15
40
  return !deepEquals(props, nextProps) || !deepEquals(state, nextState);
16
41
  }
package/src/types.ts CHANGED
@@ -212,6 +212,8 @@ export type RJSFValidationError = {
212
212
  schemaPath?: string;
213
213
  /** Full error name, for example ".name is a required property" */
214
214
  stack: string;
215
+ /** The title property for the failing field*/
216
+ title?: string;
215
217
  };
216
218
 
217
219
  /** The type that describes an error in a field */
@@ -423,6 +425,8 @@ export interface Registry<T = any, S extends StrictRJSFSchema = RJSFSchema, F ex
423
425
  translateString: (stringKey: TranslatableString, params?: string[]) => string;
424
426
  /** The optional global UI Options that are available for all templates, fields and widgets to access */
425
427
  globalUiOptions?: GlobalUISchemaOptions;
428
+ /** The component update strategy used by the Form and its fields for performance optimization */
429
+ experimental_componentUpdateStrategy?: 'customDeep' | 'shallow' | 'always';
426
430
  }
427
431
 
428
432
  /** The properties that are passed to a Field implementation */
@@ -436,8 +440,10 @@ export interface FieldProps<T = any, S extends StrictRJSFSchema = RJSFSchema, F
436
440
  formData?: T;
437
441
  /** The tree of errors for this field and its children */
438
442
  errorSchema?: ErrorSchema<T>;
439
- /** The field change event handler; called with the updated form data and an optional `ErrorSchema` */
440
- onChange: (newFormData: T | undefined, es?: ErrorSchema<T>, id?: string) => any;
443
+ /** The field change event handler; called with the updated field value, the optional change path for the value
444
+ * (defaults to an empty array), an optional ErrorSchema and the optional id of the field being changed
445
+ */
446
+ onChange: (newValue: T | undefined, path?: (number | string)[], es?: ErrorSchema<T>, id?: string) => void;
441
447
  /** The input blur event handler; call it with the field id and value */
442
448
  onBlur: (id: string, value: any) => void;
443
449
  /** The input focus event handler; call it with the field id and value */
@@ -795,7 +801,7 @@ export interface MultiSchemaFieldTemplateProps<
795
801
  export interface WidgetProps<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any>
796
802
  extends GenericObjectType,
797
803
  RJSFBaseProps<T, S, F>,
798
- Pick<HTMLAttributes<HTMLElement>, Exclude<keyof HTMLAttributes<HTMLElement>, 'onBlur' | 'onFocus'>> {
804
+ Pick<HTMLAttributes<HTMLElement>, Exclude<keyof HTMLAttributes<HTMLElement>, 'onBlur' | 'onFocus' | 'onChange'>> {
799
805
  /** The generated id for this widget, used to provide unique `name`s and `id`s for the HTML field elements rendered by
800
806
  * widgets
801
807
  */
@@ -1003,6 +1009,13 @@ export type UIOptionsType<
1003
1009
  [key: string]: boolean | number | string | object | any[] | null | undefined;
1004
1010
  };
1005
1011
 
1012
+ /**
1013
+ * A utility type that extracts the element type from an array type.
1014
+ * If the type is not an array, it returns the type itself as a safe fallback.
1015
+ * Handles both standard arrays and readonly arrays.
1016
+ */
1017
+ export type ArrayElement<A> = A extends readonly (infer E)[] ? E : A;
1018
+
1006
1019
  /** Type describing the well-known properties of the `UiSchema` while also supporting all user defined properties,
1007
1020
  * starting with `ui:`.
1008
1021
  */
@@ -1028,6 +1041,13 @@ export type UiSchema<
1028
1041
  'ui:fieldReplacesAnyOrOneOf'?: boolean;
1029
1042
  /** An object that contains all the potential UI options in a single object */
1030
1043
  'ui:options'?: UIOptionsType<T, S, F>;
1044
+ /** The uiSchema for items in an array. Can be an object for a uniform uiSchema across all items (current behavior),
1045
+ * or a function that returns a dynamic uiSchema based on the item's data and index.
1046
+ * When using a function, it receives the item data, index, and optionally the form context as parameters.
1047
+ */
1048
+ items?:
1049
+ | UiSchema<ArrayElement<T>, S, F>
1050
+ | ((itemData: ArrayElement<T>, index: number, formContext?: F) => UiSchema<ArrayElement<T>, S, F>);
1031
1051
  };
1032
1052
 
1033
1053
  /** A `CustomValidator` function takes in a `formData`, `errors` and `uiSchema` objects and returns the given `errors`