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

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 (41) hide show
  1. package/dist/index.js +95 -21
  2. package/dist/index.js.map +4 -4
  3. package/dist/utils.esm.js +95 -21
  4. package/dist/utils.esm.js.map +4 -4
  5. package/dist/utils.umd.js +93 -22
  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 +9 -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/index.d.ts +3 -1
  15. package/lib/index.js +2 -1
  16. package/lib/index.js.map +1 -1
  17. package/lib/mergeDefaultsWithFormData.js +2 -2
  18. package/lib/mergeDefaultsWithFormData.js.map +1 -1
  19. package/lib/schema/getDefaultFormState.js +10 -2
  20. package/lib/schema/getDefaultFormState.js.map +1 -1
  21. package/lib/schema/retrieveSchema.js +2 -2
  22. package/lib/schema/retrieveSchema.js.map +1 -1
  23. package/lib/shallowEquals.d.ts +8 -0
  24. package/lib/shallowEquals.js +36 -0
  25. package/lib/shallowEquals.js.map +1 -0
  26. package/lib/shouldRender.d.ts +8 -2
  27. package/lib/shouldRender.js +17 -2
  28. package/lib/shouldRender.js.map +1 -1
  29. package/lib/tsconfig.tsbuildinfo +1 -1
  30. package/lib/types.d.ts +2 -0
  31. package/package.json +1 -1
  32. package/src/constants.ts +1 -0
  33. package/src/createSchemaUtils.ts +8 -1
  34. package/src/findSchemaDefinition.ts +51 -3
  35. package/src/index.ts +4 -0
  36. package/src/mergeDefaultsWithFormData.ts +1 -1
  37. package/src/schema/getDefaultFormState.ts +12 -2
  38. package/src/schema/retrieveSchema.ts +2 -2
  39. package/src/shallowEquals.ts +41 -0
  40. package/src/shouldRender.ts +27 -2
  41. package/src/types.ts +2 -0
@@ -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
  }
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
  {},
@@ -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
@@ -423,6 +423,8 @@ export interface Registry<T = any, S extends StrictRJSFSchema = RJSFSchema, F ex
423
423
  translateString: (stringKey: TranslatableString, params?: string[]) => string;
424
424
  /** The optional global UI Options that are available for all templates, fields and widgets to access */
425
425
  globalUiOptions?: GlobalUISchemaOptions;
426
+ /** The component update strategy used by the Form and its fields for performance optimization */
427
+ experimental_componentUpdateStrategy?: 'customDeep' | 'shallow' | 'always';
426
428
  }
427
429
 
428
430
  /** The properties that are passed to a Field implementation */